mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Merge fx-team to m-c.
This commit is contained in:
commit
dae949d346
@ -1030,7 +1030,7 @@ SocialToolbar = {
|
||||
if (!toolbarButton) {
|
||||
toolbarButton = document.createElement("toolbarbutton");
|
||||
toolbarButton.setAttribute("type", "badged");
|
||||
toolbarButton.classList.add("toolbarbutton-1");
|
||||
toolbarButton.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
|
||||
toolbarButton.setAttribute("id", toolbarButtonId);
|
||||
toolbarButton.setAttribute("notificationFrameId", notificationFrameId);
|
||||
toolbarButton.addEventListener("mousedown", function (event) {
|
||||
@ -1479,7 +1479,7 @@ SocialStatus = {
|
||||
return null;
|
||||
let palette = document.getElementById("navigator-toolbox").palette;
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("class", "toolbarbutton-1 social-status-button");
|
||||
button.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional social-status-button");
|
||||
button.setAttribute("type", "badged");
|
||||
button.setAttribute("removable", "true");
|
||||
button.setAttribute("image", provider.iconURL);
|
||||
@ -1763,7 +1763,7 @@ SocialMarks = {
|
||||
let palette = document.getElementById("navigator-toolbox").palette;
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("type", "socialmark");
|
||||
button.setAttribute("class", "toolbarbutton-1 social-mark-button");
|
||||
button.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional social-mark-button");
|
||||
button.style.listStyleImage = "url(" + provider.iconURL + ")";
|
||||
button.setAttribute("origin", provider.origin);
|
||||
button.setAttribute("id", this._toolbarHelper.idFromOrgin(provider.origin));
|
||||
|
@ -860,6 +860,84 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
|
||||
-moz-image-region: auto;
|
||||
}
|
||||
|
||||
/* Customize mode */
|
||||
#tab-view-deck {
|
||||
transition-property: padding;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
#tab-view-deck[fastcustomizeanimation] {
|
||||
transition-duration: 1ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
#PanelUI-contents > .panel-customization-placeholder > .panel-customization-placeholder-child {
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
#customization-panelHolder {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#customization-panelWrapper,
|
||||
#customization-panelWrapper > .panel-arrowcontent {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#customization-panelWrapper > .panel-arrowcontent {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#customization-panelHolder > #PanelUI-mainView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Hack alert - by manually setting the preferred height to 0, we convince
|
||||
#PanelUI-mainView to shrink when the window gets smaller in customization
|
||||
mode. Not sure why that is - might have to do with our intermingling of
|
||||
XUL flex, and CSS3 Flexbox. */
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#customization-panelHolder > #PanelUI-mainView > #PanelUI-contents-scroller {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#customization-panel-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[dragover] {
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
#customization-palette:not([hidden]) {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
min-height: 3em;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[place="palette"] {
|
||||
width: 110px;
|
||||
height: 94px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[place="palette"][hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#customization-palette .toolbarpaletteitem-box {
|
||||
-moz-box-pack: center;
|
||||
-moz-box-flex: 1;
|
||||
width: 110px;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
/* UI Tour */
|
||||
|
||||
@keyframes uitour-wobble {
|
||||
|
@ -1917,6 +1917,7 @@ let CustomizableUIInternal = {
|
||||
|
||||
let currentPlacements = gPlacements.get(areaId);
|
||||
// We're excluding all of the placement IDs for items that do not exist,
|
||||
// and items that have removable="false",
|
||||
// because we don't want to consider them when determining if we're
|
||||
// in the default state. This way, if an add-on introduces a widget
|
||||
// and is then uninstalled, the leftover placement doesn't cause us to
|
||||
@ -1924,20 +1925,24 @@ let CustomizableUIInternal = {
|
||||
let buildAreaNodes = gBuildAreas.get(areaId);
|
||||
if (buildAreaNodes && buildAreaNodes.size) {
|
||||
let container = [...buildAreaNodes][0];
|
||||
let removableOrDefault = (itemNodeOrItem) => {
|
||||
let item = (itemNodeOrItem && itemNodeOrItem.id) || itemNodeOrItem;
|
||||
let isRemovable = this.isWidgetRemovable(itemNodeOrItem);
|
||||
let isInDefault = defaultPlacements.indexOf(item) != -1;
|
||||
return isRemovable || isInDefault;
|
||||
};
|
||||
// Toolbars have a currentSet property which also deals correctly with overflown
|
||||
// widgets (if any) - use that instead:
|
||||
if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
|
||||
currentPlacements = container.currentSet.split(',');
|
||||
currentPlacements = currentPlacements.filter(removableOrDefault);
|
||||
} else {
|
||||
// Clone the array so we don't modify the actual placements...
|
||||
currentPlacements = [...currentPlacements];
|
||||
// Loop backwards through the placements so we can easily remove items:
|
||||
let itemIndex = currentPlacements.length;
|
||||
while (itemIndex--) {
|
||||
if (!container.querySelector(idToSelector(currentPlacements[itemIndex]))) {
|
||||
currentPlacements.splice(itemIndex, 1);
|
||||
}
|
||||
}
|
||||
currentPlacements = currentPlacements.filter((item) => {
|
||||
let itemNode = container.querySelector(idToSelector(item));
|
||||
return itemNode && removableOrDefault(itemNode || item);
|
||||
});
|
||||
}
|
||||
}
|
||||
LOG("Checking default state for " + areaId + ":\n" + currentPlacements.join(",") +
|
||||
|
@ -657,6 +657,9 @@ CustomizeMode.prototype = {
|
||||
|
||||
reset: function() {
|
||||
this.resetting = true;
|
||||
// Disable the reset button temporarily while resetting:
|
||||
let btn = this.document.getElementById("customization-reset-button");
|
||||
btn.disabled = true;
|
||||
return Task.spawn(function() {
|
||||
this._removePanelCustomizationPlaceholders();
|
||||
yield this.depopulatePalette();
|
||||
|
@ -37,6 +37,7 @@ skip-if = true
|
||||
skip-if = os == "mac"
|
||||
|
||||
[browser_938980_navbar_collapsed.js]
|
||||
[browser_938995_indefaultstate_nonremovable.js]
|
||||
[browser_940946_removable_from_navbar_customizemode.js]
|
||||
[browser_941083_invalidate_wrapper_cache_createWidget.js]
|
||||
[browser_panel_toggle.js]
|
||||
|
@ -0,0 +1,33 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const kWidgetId = "test-non-removable-widget";
|
||||
let gTests = [
|
||||
{
|
||||
desc: "Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState",
|
||||
run: function() {
|
||||
let navbar = document.getElementById("nav-bar");
|
||||
ok(CustomizableUI.inDefaultState, "Should start in default state");
|
||||
|
||||
CustomizableUI.createWidget({id: kWidgetId, removable: false, label: "Test"});
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
|
||||
ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
|
||||
CustomizableUI.createWidget({id: kWidgetId, removable: false, label: "Test"});
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
|
||||
ok(CustomizableUI.inDefaultState, "Should still be in default state after panel addition");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state after destroying both widgets");
|
||||
},
|
||||
teardown: null
|
||||
},
|
||||
];
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
runTests(gTests);
|
||||
}
|
||||
|
@ -100,7 +100,6 @@ exports.testBasic = function(options) {
|
||||
'Saluton', 'Sawubona'
|
||||
],
|
||||
unassigned: [ ],
|
||||
tooltipState: 'true:importantFieldFlag',
|
||||
args: {
|
||||
command: { name: 'tsslow' },
|
||||
hello: {
|
||||
@ -123,7 +122,6 @@ exports.testBasic = function(options) {
|
||||
status: 'ERROR',
|
||||
predictions: [ 'Shalom', 'Saluton', 'Sawubona', 'Namasté' ],
|
||||
unassigned: [ ],
|
||||
tooltipState: 'true:importantFieldFlag',
|
||||
args: {
|
||||
command: { name: 'tsslow' },
|
||||
hello: {
|
||||
@ -147,7 +145,6 @@ exports.testBasic = function(options) {
|
||||
status: 'VALID',
|
||||
predictions: [ 'Shalom' ],
|
||||
unassigned: [ ],
|
||||
tooltipState: 'true:importantFieldFlag',
|
||||
args: {
|
||||
command: { name: 'tsslow' },
|
||||
hello: {
|
||||
|
@ -290,8 +290,6 @@ exports.testNodes = function(options) {
|
||||
cursor: 16,
|
||||
current: 'nodes',
|
||||
status: 'ERROR',
|
||||
outputState: 'false:default',
|
||||
tooltipState: 'true:isError',
|
||||
args: {
|
||||
command: { name: 'tse' },
|
||||
node: {
|
||||
@ -327,8 +325,6 @@ exports.testNodes = function(options) {
|
||||
cursor: 17,
|
||||
current: 'nodes2',
|
||||
status: 'ERROR',
|
||||
outputState: 'false:default',
|
||||
tooltipState: 'false:default',
|
||||
args: {
|
||||
command: { name: 'tse' },
|
||||
node: {
|
||||
|
@ -124,7 +124,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
}
|
||||
|
||||
// Create a new chrome debugging profile.
|
||||
this._dbgProfile = profileService.createProfile(null, null, profileName);
|
||||
this._dbgProfile = profileService.createProfile(null, profileName);
|
||||
profileService.flush();
|
||||
|
||||
dumpn("Finished creating the chrome debugger user profile.");
|
||||
|
@ -31,60 +31,44 @@ var APZCObserver = {
|
||||
os.addObserver(this, "apzc-handle-pan-begin", false);
|
||||
os.addObserver(this, "apzc-handle-pan-end", false);
|
||||
|
||||
// Fired by ContentAreaObserver
|
||||
window.addEventListener("SizeChanged", this, true);
|
||||
|
||||
Elements.tabList.addEventListener("TabSelect", this, true);
|
||||
Elements.tabList.addEventListener("TabOpen", this, true);
|
||||
Elements.tabList.addEventListener("TabClose", this, true);
|
||||
},
|
||||
|
||||
shutdown: function shutdown() {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let os = Services.obs;
|
||||
os.removeObserver(this, "apzc-handle-pan-begin");
|
||||
os.removeObserver(this, "apzc-handle-pan-end");
|
||||
|
||||
window.removeEventListener("SizeChanged", this, true);
|
||||
|
||||
Elements.tabList.removeEventListener("TabSelect", this, true);
|
||||
Elements.tabList.removeEventListener("TabOpen", this, true);
|
||||
Elements.tabList.removeEventListener("TabClose", this, true);
|
||||
},
|
||||
|
||||
handleEvent: function APZC_handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "SizeChanged":
|
||||
case 'TabSelect':
|
||||
this._resetDisplayPort();
|
||||
break;
|
||||
|
||||
case 'pageshow':
|
||||
if (aEvent.target != Browser.selectedBrowser.contentDocument) {
|
||||
break;
|
||||
}
|
||||
// intentional fall through
|
||||
case 'TabSelect': {
|
||||
// Start off with something reasonable. The apzc will handle these
|
||||
// calculations once scrolling starts.
|
||||
let doc = Browser.selectedBrowser.contentDocument.documentElement;
|
||||
// While running tests, sometimes this can be null. If we don't have a
|
||||
// root document, there's no point in setting a scrollable display port.
|
||||
if (!doc) {
|
||||
break;
|
||||
}
|
||||
let win = Browser.selectedBrowser.contentWindow;
|
||||
let factor = 0.2;
|
||||
let portX = 0;
|
||||
let portY = 0;
|
||||
let portWidth = ContentAreaObserver.width;
|
||||
let portHeight = ContentAreaObserver.height;
|
||||
|
||||
if (portWidth < doc.scrollWidth) {
|
||||
portWidth += ContentAreaObserver.width * factor;
|
||||
if (portWidth > doc.scrollWidth) {
|
||||
portWidth = doc.scrollWidth;
|
||||
}
|
||||
}
|
||||
if (portHeight < doc.scrollHeight) {
|
||||
portHeight += ContentAreaObserver.height * factor;
|
||||
if (portHeight > doc.scrollHeight) {
|
||||
portHeight = doc.scrollHeight;
|
||||
}
|
||||
}
|
||||
if (win.scrollX > 0) {
|
||||
portX -= ContentAreaObserver.width * factor;
|
||||
}
|
||||
if (win.scrollY > 0) {
|
||||
portY -= ContentAreaObserver.height * factor;
|
||||
}
|
||||
let cwu = Browser.selectedBrowser.contentWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
cwu.setDisplayPortForElement(portX, portY,
|
||||
portWidth, portHeight,
|
||||
Browser.selectedBrowser.contentDocument.documentElement);
|
||||
this._resetDisplayPort();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'TabOpen': {
|
||||
let browser = aEvent.originalTarget.linkedBrowser;
|
||||
browser.addEventListener("pageshow", this, true);
|
||||
@ -101,19 +85,6 @@ var APZCObserver = {
|
||||
}
|
||||
},
|
||||
|
||||
shutdown: function shutdown() {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
Elements.tabList.removeEventListener("TabSelect", this, true);
|
||||
Elements.tabList.removeEventListener("TabOpen", this, true);
|
||||
Elements.tabList.removeEventListener("TabClose", this, true);
|
||||
|
||||
let os = Services.obs;
|
||||
os.removeObserver(this, "apzc-handle-pan-begin");
|
||||
os.removeObserver(this, "apzc-handle-pan-end");
|
||||
},
|
||||
|
||||
observe: function ao_observe(aSubject, aTopic, aData) {
|
||||
if (aTopic == "apzc-handle-pan-begin") {
|
||||
// When we're panning, hide the main scrollbars by setting imprecise
|
||||
@ -140,5 +111,47 @@ var APZCObserver = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_resetDisplayPort: function () {
|
||||
// Start off with something reasonable. The apzc will handle these
|
||||
// calculations once scrolling starts.
|
||||
let doc = Browser.selectedBrowser.contentDocument.documentElement;
|
||||
// While running tests, sometimes this can be null. If we don't have a
|
||||
// root document, there's no point in setting a scrollable display port.
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
let win = Browser.selectedBrowser.contentWindow;
|
||||
let factor = 0.2;
|
||||
let portX = 0;
|
||||
let portY = 0;
|
||||
let portWidth = ContentAreaObserver.width;
|
||||
let portHeight = ContentAreaObserver.height;
|
||||
|
||||
if (portWidth < doc.scrollWidth) {
|
||||
portWidth += ContentAreaObserver.width * factor;
|
||||
if (portWidth > doc.scrollWidth) {
|
||||
portWidth = doc.scrollWidth;
|
||||
}
|
||||
}
|
||||
if (portHeight < doc.scrollHeight) {
|
||||
portHeight += ContentAreaObserver.height * factor;
|
||||
if (portHeight > doc.scrollHeight) {
|
||||
portHeight = doc.scrollHeight;
|
||||
}
|
||||
}
|
||||
if (win.scrollX > 0) {
|
||||
portX -= ContentAreaObserver.width * factor;
|
||||
}
|
||||
if (win.scrollY > 0) {
|
||||
portY -= ContentAreaObserver.height * factor;
|
||||
}
|
||||
let cwu = Browser.selectedBrowser.contentWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
cwu.setDisplayPortForElement(portX, portY,
|
||||
portWidth, portHeight,
|
||||
Browser.selectedBrowser.contentDocument.documentElement);
|
||||
}
|
||||
};
|
||||
|
@ -66,6 +66,10 @@
|
||||
this.classList.remove("flyoutpanel-slide-in");
|
||||
this.setAttribute("isSlidingOut", true);
|
||||
this.removeAttribute("isSlidIn");
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("MozFlyoutPanelHiding", true, false);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -454,10 +454,14 @@ var BrowserUI = {
|
||||
openLinkInNewTab: function (aURI, aBringFront, aOwner) {
|
||||
ContextUI.peekTabs(aBringFront ? kForegroundTabAnimationDelay
|
||||
: kBackgroundTabAnimationDelay);
|
||||
let tab = Browser.addTab(aURI, aBringFront, aOwner, {
|
||||
referrerURI: aOwner.browser.documentURI,
|
||||
charset: aOwner.browser.characterSet,
|
||||
});
|
||||
let params = null;
|
||||
if (aOwner) {
|
||||
params = {
|
||||
referrerURI: aOwner.browser.documentURI,
|
||||
charset: aOwner.browser.characterSet,
|
||||
};
|
||||
}
|
||||
let tab = Browser.addTab(aURI, aBringFront, aOwner, params);
|
||||
Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab);
|
||||
return tab;
|
||||
},
|
||||
|
@ -39,6 +39,7 @@ let AboutFlyoutPanel = {
|
||||
}
|
||||
|
||||
window.addEventListener('MozFlyoutPanelShowing', this, false);
|
||||
window.addEventListener('MozFlyoutPanelHiding', this, false);
|
||||
|
||||
#if MOZ_UPDATE_CHANNEL != release
|
||||
let defaults = Services.prefs.getDefaultBranch("");
|
||||
@ -59,9 +60,13 @@ let AboutFlyoutPanel = {
|
||||
switch (aEvent.type) {
|
||||
case 'MozFlyoutPanelShowing':
|
||||
#ifdef MOZ_UPDATER
|
||||
onUnload();
|
||||
this.appUpdater = new appUpdater();
|
||||
gAppUpdater = this.appUpdater;
|
||||
#endif
|
||||
break;
|
||||
case 'MozFlyoutPanelHiding':
|
||||
#ifdef MOZ_UPDATER
|
||||
onUnload();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@ -83,7 +88,7 @@ function onUnload(aEvent) {
|
||||
// Safe to call even when there isn't a download in progress.
|
||||
gAppUpdater.removeDownloadListener();
|
||||
gAppUpdater = null;
|
||||
AboutFlyout.appUpdater = null;
|
||||
AboutFlyoutPanel.appUpdater = null;
|
||||
}
|
||||
|
||||
function appUpdater()
|
||||
|
@ -19,9 +19,8 @@
|
||||
.devtools-toolbar {
|
||||
-moz-appearance: none;
|
||||
padding: 4px 3px;
|
||||
color: hsl(210,30%,85%);
|
||||
background-color: #343c45;
|
||||
border-bottom: 1px solid #060a0d;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.devtools-menulist,
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
.devtools-monospace {
|
||||
font-family: Menlo, monospace;
|
||||
font-size: 108%;
|
||||
}
|
||||
|
||||
/* Toolbar and Toolbar items */
|
||||
@ -21,9 +20,8 @@
|
||||
.devtools-toolbar {
|
||||
-moz-appearance: none;
|
||||
padding: 4px 3px;
|
||||
color: hsl(210,30%,85%);
|
||||
background-color: #343c45;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.devtools-menulist,
|
||||
|
@ -3,18 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Customization mode */
|
||||
|
||||
#tab-view-deck {
|
||||
transition-property: padding;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
#tab-view-deck[fastcustomizeanimation] {
|
||||
transition-duration: 1ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
#nav-bar[customize-entered] > #nav-bar-customization-target {
|
||||
margin: 1px 3px;
|
||||
}
|
||||
@ -34,35 +22,6 @@
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
#PanelUI-contents > .panel-customization-placeholder > .panel-customization-placeholder-child {
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
#customization-panelHolder {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#customization-panelWrapper,
|
||||
#customization-panelWrapper > .panel-arrowcontent {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#customization-panelHolder > #PanelUI-mainView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Hack alert - by manually setting the preferred height to 0, we convince
|
||||
#PanelUI-mainView to shrink when the window gets smaller in customization
|
||||
mode. Not sure why that is - might have to do with our intermingling of
|
||||
XUL flex, and CSS3 Flexbox. */
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#customization-panelHolder > #PanelUI-mainView > #PanelUI-contents-scroller {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] .customization-target {
|
||||
min-width: 100px;
|
||||
padding-left: 10px;
|
||||
@ -96,11 +55,6 @@
|
||||
background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
|
||||
background-size: auto 12px, 12px 100%, auto, auto, auto;
|
||||
background-attachment: scroll, scroll, fixed, fixed, scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#customization-panelWrapper > .panel-arrowcontent {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
toolbarpaletteitem {
|
||||
@ -146,41 +100,15 @@ toolbarpaletteitem[place="toolbar"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[dragover] {
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
#customization-palette:not([hidden]) {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
min-height: 3em;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[place="palette"] {
|
||||
width: 110px;
|
||||
height: 94px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
toolbarpaletteitem[place="palette"][hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#customization-palette > #wrapper-edit-controls,
|
||||
#customization-palette > #wrapper-zoom-controls {
|
||||
width: 225px;
|
||||
}
|
||||
|
||||
#customization-palette .toolbarpaletteitem-box {
|
||||
-moz-box-pack: center;
|
||||
-moz-box-flex: 1;
|
||||
width: 110px;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
#wrapper-edit-controls[place="palette"] > .toolbarpaletteitem-box,
|
||||
#wrapper-zoom-controls[place="palette"] > .toolbarpaletteitem-box {
|
||||
width: 225px;
|
||||
|
@ -135,9 +135,8 @@
|
||||
.theme-toolbar,
|
||||
.devtools-toolbar { /* General toolbar styling */
|
||||
color: hsl(210,30%,85%);
|
||||
background-image: url(background-noise-toolbar.png), linear-gradient(#3e4750, #3e4750);
|
||||
background-color: #343c45;
|
||||
border-color: #060a0d;
|
||||
box-shadow: 0 1px 0 hsla(204,45%,98%,.05) inset, -1px 0 0 hsla(204,45%,98%,.05) inset, 0 -1px 0 hsla(204,45%,98%,.05) inset;
|
||||
}
|
||||
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
|
@ -138,9 +138,8 @@
|
||||
.theme-toolbar,
|
||||
.devtools-toolbar { /* General toolbar styling */
|
||||
color: hsl(210,30%,85%);
|
||||
background-image: url(background-noise-toolbar.png), linear-gradient(#3e4750, #3e4750);
|
||||
background-color: #343c45;
|
||||
border-color: #060a0d;
|
||||
box-shadow: 0 1px 0 hsla(204,45%,98%,.05) inset, -1px 0 0 hsla(204,45%,98%,.05) inset, 0 -1px 0 hsla(204,45%,98%,.05) inset;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
%define glassActiveBorderColor rgb(37, 44, 51)
|
||||
%define glassInactiveBorderColor rgb(102, 102, 102)
|
||||
%define nested-buttons #zoom-out-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
|
||||
%define nestedButtons #zoom-out-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
|
||||
|
||||
%include downloads/indicator-aero.css
|
||||
|
||||
@ -227,10 +227,10 @@
|
||||
#TabsToolbar > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbarpaletteitem > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-lwtheme),
|
||||
#toolbar-menubar > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbarpaletteitem > toolbaritem > :-moz-any(@nested-buttons@):not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbaritem > :-moz-any(@nested-buttons@):not(:-moz-lwtheme),
|
||||
#TabsToolbar > toolbarpaletteitem > toolbaritem > :-moz-any(@nested-buttons@):not(:-moz-lwtheme),
|
||||
#TabsToolbar > toolbaritem > :-moz-any(@nested-buttons@):not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbarpaletteitem > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
|
||||
#TabsToolbar > toolbarpaletteitem > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
|
||||
#TabsToolbar > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
|
||||
#toolbar-menubar > toolbarpaletteitem > :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme),
|
||||
#toolbar-menubar > :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme),
|
||||
#TabsToolbar > toolbarpaletteitem > :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme),
|
||||
|
@ -18,9 +18,8 @@
|
||||
.devtools-toolbar {
|
||||
-moz-appearance: none;
|
||||
padding: 4px 3px;
|
||||
color: hsl(210,30%,85%);
|
||||
background-color: #343c45;
|
||||
border-bottom: 1px solid #060a0d;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.devtools-menulist,
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
@ -667,7 +668,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
}
|
||||
mHomePagerContainer.setPadding(0, 0, 0, 0);
|
||||
if (mBrowserToolbar != null) {
|
||||
mBrowserToolbar.scrollTo(0, 0);
|
||||
ViewHelper.setTranslationY(mBrowserToolbar, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -928,7 +929,8 @@ abstract public class BrowserApp extends GeckoApp
|
||||
final int marginTop = Math.round(aMetrics.marginTop);
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
public void run() {
|
||||
toolbarLayout.scrollTo(0, toolbarLayout.getHeight() - marginTop);
|
||||
ViewHelper.setTranslationY(toolbarLayout, marginTop - toolbarLayout.getHeight());
|
||||
|
||||
if (mDoorHangerPopup.isShowing()) {
|
||||
mDoorHangerPopup.updatePopup();
|
||||
}
|
||||
|
@ -48,6 +48,9 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
// Pinned state.
|
||||
private boolean mIsPinned = false;
|
||||
|
||||
// Dirty state.
|
||||
private boolean mIsDirty = false;
|
||||
|
||||
// Empty state.
|
||||
private boolean mIsEmpty = true;
|
||||
private int mLoadId = Favicons.NOT_LOADING;
|
||||
@ -146,6 +149,10 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
displayThumbnail(R.drawable.top_site_add);
|
||||
}
|
||||
|
||||
public void markAsDirty() {
|
||||
mIsDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the title, URL, and pinned state of this view.
|
||||
*
|
||||
@ -184,6 +191,12 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// The dirty state forces the state update to return true
|
||||
// so that the adapter loads favicons once the thumbnails
|
||||
// are loaded in TopSitesPage/TopSitesGridAdapter.
|
||||
changed = (changed || mIsDirty);
|
||||
mIsDirty = false;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
@ -558,6 +558,17 @@ public class TopSitesPage extends HomeFragment {
|
||||
*/
|
||||
public void updateThumbnails(Map<String, Bitmap> thumbnails) {
|
||||
mThumbnails = thumbnails;
|
||||
|
||||
final int count = mGrid.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
TopSitesGridItemView gridItem = (TopSitesGridItemView) mGrid.getChildAt(i);
|
||||
|
||||
// All the views have already got their initial state at this point.
|
||||
// This will force each view to load favicons for the missing
|
||||
// thumbnails if necessary.
|
||||
gridItem.markAsDirty();
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@ -578,7 +589,11 @@ public class TopSitesPage extends HomeFragment {
|
||||
|
||||
// If there is no url, then show "add bookmark".
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
view.blankOut();
|
||||
// Wait until thumbnails are loaded before showing anything.
|
||||
if (mThumbnails != null) {
|
||||
view.blankOut();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -589,8 +604,9 @@ public class TopSitesPage extends HomeFragment {
|
||||
// fetches.
|
||||
final boolean updated = view.updateState(title, url, pinned, thumbnail);
|
||||
|
||||
// If we sent in a thumbnail, we're done now.
|
||||
if (thumbnail != null) {
|
||||
// If thumbnails are still being loaded, don't try to load favicons
|
||||
// just yet. If we sent in a thumbnail, we're done now.
|
||||
if (mThumbnails == null || thumbnail != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -289,12 +289,13 @@ gbjar.sources += [
|
||||
'toolbar/AutocompleteHandler.java',
|
||||
'toolbar/BackButton.java',
|
||||
'toolbar/BrowserToolbar.java',
|
||||
'toolbar/BrowserToolbarBackground.java',
|
||||
'toolbar/CanvasDelegate.java',
|
||||
'toolbar/ForwardButton.java',
|
||||
'toolbar/PageActionLayout.java',
|
||||
'toolbar/ShapedButton.java',
|
||||
'toolbar/TabCounter.java',
|
||||
'toolbar/ToolbarEditLayout.java',
|
||||
'toolbar/ToolbarEditText.java',
|
||||
'TouchEventInterceptor.java',
|
||||
'updater/UpdateService.java',
|
||||
'updater/UpdateServiceHelper.java',
|
||||
@ -899,6 +900,7 @@ ANDROID_RESFILES += [
|
||||
'resources/layout/tabs_panel_header.xml',
|
||||
'resources/layout/tabs_panel_indicator.xml',
|
||||
'resources/layout/text_selection_handles.xml',
|
||||
'resources/layout/toolbar_edit_layout.xml',
|
||||
'resources/layout/top_sites_grid_item_view.xml',
|
||||
'resources/layout/two_line_page_row.xml',
|
||||
'resources/layout/validation_message.xml',
|
||||
|
@ -6,11 +6,6 @@
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<org.mozilla.gecko.toolbar.BrowserToolbarBackground android:id="@+id/url_bar_bg"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/url_bar_bg"/>
|
||||
|
||||
<org.mozilla.gecko.toolbar.ShapedButton android:id="@+id/tabs"
|
||||
style="@style/UrlBar.ImageButton"
|
||||
android:layout_width="84dip"
|
||||
@ -65,43 +60,14 @@
|
||||
android:contentDescription="@string/back"
|
||||
android:background="@drawable/url_bar_nav_button"/>
|
||||
|
||||
<LinearLayout android:id="@+id/url_edit_container"
|
||||
<org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
|
||||
style="@style/UrlBar.Button"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="4dp"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal"
|
||||
android:layout_toRightOf="@id/back"
|
||||
android:layout_toLeftOf="@id/menu_items">
|
||||
|
||||
<view class="org.mozilla.gecko.CustomEditText"
|
||||
android:id="@+id/url_edit_text"
|
||||
style="@style/UrlBar.Button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/url_bar_default_text"
|
||||
android:textColor="@color/url_bar_title"
|
||||
android:textColorHint="@color/url_bar_title_hint"
|
||||
android:textColorHighlight="@color/url_bar_text_highlight"
|
||||
android:textSelectHandle="@drawable/handle_middle"
|
||||
android:textSelectHandleLeft="@drawable/handle_start"
|
||||
android:textSelectHandleRight="@drawable/handle_end"
|
||||
android:textCursorDrawable="@null"
|
||||
android:inputType="textUri|textNoSuggestions"
|
||||
android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical|left"
|
||||
gecko:autoUpdateTheme="false"/>
|
||||
|
||||
<ImageButton android:id="@+id/go"
|
||||
style="@style/UrlBar.ImageButton.Icon"
|
||||
android:src="@drawable/ic_url_bar_go"
|
||||
android:contentDescription="@string/go"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_toLeftOf="@id/menu_items"/>
|
||||
|
||||
<LinearLayout android:id="@+id/url_display_container"
|
||||
style="@style/UrlBar.Button.Container"
|
||||
|
@ -12,11 +12,6 @@
|
||||
<ImageButton android:id="@+id/forward"
|
||||
style="@style/UrlBar.ImageButton.Unused"/>
|
||||
|
||||
<org.mozilla.gecko.toolbar.BrowserToolbarBackground android:id="@+id/url_bar_bg"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/url_bar_bg"/>
|
||||
|
||||
<ImageView android:id="@+id/url_bar_entry"
|
||||
style="@style/UrlBar.Button"
|
||||
android:layout_marginLeft="4dp"
|
||||
@ -42,6 +37,7 @@
|
||||
android:duplicateParentState="true"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="invisible"
|
||||
android:src="@drawable/url_bar_right_edge"
|
||||
android:scaleType="fitXY"/>
|
||||
|
||||
@ -90,42 +86,12 @@
|
||||
android:layout_marginTop="12dip"
|
||||
android:layout_alignRight="@id/tabs"/>
|
||||
|
||||
<LinearLayout android:id="@+id/url_edit_container"
|
||||
<org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
|
||||
style="@style/UrlBar.Button"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.mozilla.gecko.CustomEditText
|
||||
android:id="@+id/url_edit_text"
|
||||
style="@style/UrlBar.Button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/url_bar_default_text"
|
||||
android:textColor="@color/url_bar_title"
|
||||
android:textColorHint="@color/url_bar_title_hint"
|
||||
android:textColorHighlight="@color/url_bar_text_highlight"
|
||||
android:textSelectHandle="@drawable/handle_middle"
|
||||
android:textSelectHandleLeft="@drawable/handle_start"
|
||||
android:textSelectHandleRight="@drawable/handle_end"
|
||||
android:textCursorDrawable="@null"
|
||||
android:inputType="textUri|textNoSuggestions"
|
||||
android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical|left"
|
||||
gecko:autoUpdateTheme="false"/>
|
||||
|
||||
<ImageButton android:id="@+id/go"
|
||||
style="@style/UrlBar.ImageButton.Icon"
|
||||
android:src="@drawable/ic_url_bar_go"
|
||||
android:contentDescription="@string/go"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout android:id="@+id/url_display_container"
|
||||
style="@style/UrlBar.Button"
|
||||
|
@ -73,7 +73,8 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="@dimen/browser_toolbar_height"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
android:focusable="true"
|
||||
android:background="@drawable/url_bar_bg"/>
|
||||
|
||||
</view>
|
||||
|
||||
|
36
mobile/android/base/resources/layout/toolbar_edit_layout.xml
Normal file
36
mobile/android/base/resources/layout/toolbar_edit_layout.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<org.mozilla.gecko.toolbar.ToolbarEditText
|
||||
android:id="@+id/url_edit_text"
|
||||
style="@style/UrlBar.Button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/url_bar_default_text"
|
||||
android:textColor="@color/url_bar_title"
|
||||
android:textColorHint="@color/url_bar_title_hint"
|
||||
android:textColorHighlight="@color/url_bar_text_highlight"
|
||||
android:textSelectHandle="@drawable/handle_middle"
|
||||
android:textSelectHandleLeft="@drawable/handle_start"
|
||||
android:textSelectHandleRight="@drawable/handle_end"
|
||||
android:textCursorDrawable="@null"
|
||||
android:inputType="textUri|textNoSuggestions"
|
||||
android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical|left"
|
||||
gecko:autoUpdateTheme="false"/>
|
||||
|
||||
<ImageButton android:id="@+id/go"
|
||||
style="@style/UrlBar.ImageButton.Icon"
|
||||
android:src="@drawable/ic_url_bar_go"
|
||||
android:contentDescription="@string/go"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</merge>
|
@ -7,8 +7,6 @@ package org.mozilla.gecko.toolbar;
|
||||
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.CustomEditText;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
@ -18,18 +16,14 @@ import org.mozilla.gecko.SiteIdentityPopup;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.MenuPopup;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.widget.GeckoImageButton;
|
||||
@ -42,22 +36,15 @@ import org.json.JSONObject;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
@ -68,33 +55,24 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
import android.view.Window;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.RelativeLayout.LayoutParams;
|
||||
import android.widget.ViewSwitcher;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class BrowserToolbar extends GeckoRelativeLayout
|
||||
implements TextWatcher,
|
||||
AutocompleteHandler,
|
||||
Tabs.OnTabsChangedListener,
|
||||
implements Tabs.OnTabsChangedListener,
|
||||
GeckoMenu.ActionItemBarPresenter,
|
||||
Animation.AnimationListener,
|
||||
GeckoEventListener {
|
||||
@ -127,11 +105,9 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
|
||||
private View mUrlDisplayContainer;
|
||||
private View mUrlEditContainer;
|
||||
private CustomEditText mUrlEditText;
|
||||
private ToolbarEditLayout mUrlEditLayout;
|
||||
private View mUrlBarEntry;
|
||||
private ImageView mUrlBarRightEdge;
|
||||
private BrowserToolbarBackground mUrlBarBackground;
|
||||
private GeckoTextView mTitle;
|
||||
private int mTitlePadding;
|
||||
private boolean mSiteSecurityVisible;
|
||||
@ -142,7 +118,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
private ImageButton mFavicon;
|
||||
private ImageButton mStop;
|
||||
private ImageButton mSiteSecurity;
|
||||
private ImageButton mGo;
|
||||
private PageActionLayout mPageActionLayout;
|
||||
private Animation mProgressSpinner;
|
||||
private TabCounter mTabsCounter;
|
||||
@ -165,12 +140,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
private boolean mShowSiteSecurity;
|
||||
private boolean mSpinnerVisible;
|
||||
|
||||
private boolean mDelayRestartInput;
|
||||
// The previous autocomplete result returned to us
|
||||
private String mAutoCompleteResult = "";
|
||||
// The user typed part of the autocomplete result
|
||||
private String mAutoCompletePrefix = null;
|
||||
|
||||
private boolean mIsEditing;
|
||||
private boolean mAnimatingEntry;
|
||||
|
||||
@ -196,6 +165,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
private final ForegroundColorSpan mDomainColor;
|
||||
private final ForegroundColorSpan mPrivateDomainColor;
|
||||
|
||||
private final LightweightTheme mTheme;
|
||||
|
||||
private boolean mShowUrl;
|
||||
private boolean mTrimURLs;
|
||||
|
||||
@ -207,6 +178,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
|
||||
public BrowserToolbar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
// BrowserToolbar is attached to BrowserApp only.
|
||||
mActivity = (BrowserApp) context;
|
||||
@ -217,7 +189,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
mSwitchingTabs = true;
|
||||
|
||||
mIsEditing = false;
|
||||
mAnimatingEntry = false;
|
||||
mShowUrl = false;
|
||||
mTrimURLs = true;
|
||||
@ -283,14 +254,11 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
|
||||
mAnimatingEntry = false;
|
||||
|
||||
mUrlBarBackground = (BrowserToolbarBackground) findViewById(R.id.url_bar_bg);
|
||||
mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
|
||||
mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
|
||||
mUrlDisplayContainer = findViewById(R.id.url_display_container);
|
||||
mUrlBarEntry = findViewById(R.id.url_bar_entry);
|
||||
|
||||
mUrlEditContainer = findViewById(R.id.url_edit_container);
|
||||
mUrlEditText = (CustomEditText) findViewById(R.id.url_edit_text);
|
||||
mUrlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
|
||||
|
||||
// This will clip the right edge's image at 60% of its width
|
||||
mUrlBarRightEdge = (ImageView) findViewById(R.id.url_bar_right_edge);
|
||||
@ -339,6 +307,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
mFocusOrder = Arrays.asList(this, mSiteSecurity, mPageActionLayout, mStop,
|
||||
mTabs, mMenu);
|
||||
}
|
||||
|
||||
setIsEditing(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -398,94 +368,10 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
});
|
||||
|
||||
mUrlEditText.addTextChangedListener(this);
|
||||
|
||||
mUrlEditText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() {
|
||||
@Override
|
||||
public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) {
|
||||
// We only want to process one event per tap
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN)
|
||||
return false;
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
// If the edit text has a composition string, don't submit the text yet.
|
||||
// ENTER is needed to commit the composition string.
|
||||
Editable content = mUrlEditText.getText();
|
||||
if (!hasCompositionString(content)) {
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// Drop the virtual keyboard.
|
||||
clearFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mUrlEditText.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) {
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
return true;
|
||||
} else if (GamepadUtils.isBackKey(event)) {
|
||||
if (mDismissListener != null) {
|
||||
mDismissListener.onDismiss();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mUrlEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
mUrlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (v == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelected(hasFocus);
|
||||
if (hasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
try {
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(LOGTAG, "InputMethodManagerService, why are you throwing"
|
||||
+ " a NullPointerException? See bug 782096", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mUrlEditText.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
CustomEditText text = (CustomEditText) v;
|
||||
|
||||
if (text.getSelectionStart() == text.getSelectionEnd())
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@ -553,16 +439,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
});
|
||||
|
||||
mGo = (ImageButton) findViewById(R.id.go);
|
||||
mGo.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
|
||||
|
||||
LinearLayout.LayoutParams siteSecParams = (LinearLayout.LayoutParams) mSiteSecurity.getLayoutParams();
|
||||
@ -621,24 +497,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
return false;
|
||||
} else if (isEditing()) {
|
||||
final int prevSelStart = mUrlEditText.getSelectionStart();
|
||||
final int prevSelEnd = mUrlEditText.getSelectionEnd();
|
||||
|
||||
// Manually dispatch the key event to the edit text. If selection changed as
|
||||
// a result of the key event, then give focus back to mUrlEditText
|
||||
mUrlEditText.dispatchKeyEvent(event);
|
||||
|
||||
final int curSelStart = mUrlEditText.getSelectionStart();
|
||||
final int curSelEnd = mUrlEditText.getSelectionEnd();
|
||||
|
||||
if (prevSelStart != curSelStart || prevSelEnd != curSelEnd) {
|
||||
mUrlEditText.requestFocusFromTouch();
|
||||
|
||||
// Restore the selection, which gets lost due to the focus switch
|
||||
mUrlEditText.setSelection(curSelStart, curSelEnd);
|
||||
}
|
||||
|
||||
return true;
|
||||
return mUrlEditLayout.onKey(keyCode, event);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -648,7 +507,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// If the motion event has occured below the toolbar (due to the scroll
|
||||
// offset), let it pass through to the page.
|
||||
if (event != null && event.getY() > getHeight() - getScrollY()) {
|
||||
if (event != null && event.getY() > getHeight() + ViewHelper.getTranslationY(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -743,88 +602,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
}
|
||||
|
||||
// Return early if we're backspacing through the string, or
|
||||
// have no autocomplete results
|
||||
@Override
|
||||
public void onAutocomplete(final String result) {
|
||||
if (!isEditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = mUrlEditText.getText().toString();
|
||||
|
||||
if (result == null) {
|
||||
mAutoCompleteResult = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.startsWith(text) || text.equals(result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mAutoCompleteResult = result;
|
||||
mUrlEditText.getText().append(result.substring(text.length()));
|
||||
mUrlEditText.setSelection(text.length(), result.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if (!isEditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = s.toString();
|
||||
boolean useHandler = false;
|
||||
boolean reuseAutocomplete = false;
|
||||
if (!hasCompositionString(s) && !StringUtils.isSearchQuery(text, false)) {
|
||||
useHandler = true;
|
||||
|
||||
// If you're hitting backspace (the string is getting smaller
|
||||
// or is unchanged), don't autocomplete.
|
||||
if (mAutoCompletePrefix != null && (mAutoCompletePrefix.length() >= text.length())) {
|
||||
useHandler = false;
|
||||
} else if (mAutoCompleteResult != null && mAutoCompleteResult.startsWith(text)) {
|
||||
// If this text already matches our autocomplete text, autocomplete likely
|
||||
// won't change. Just reuse the old autocomplete value.
|
||||
useHandler = false;
|
||||
reuseAutocomplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the autocomplete text being set, don't run the filter.
|
||||
if (TextUtils.isEmpty(mAutoCompleteResult) || !mAutoCompleteResult.equals(text)) {
|
||||
if (isEditing() && mFilterListener != null) {
|
||||
mFilterListener.onFilter(text, useHandler ? this : null);
|
||||
}
|
||||
mAutoCompletePrefix = text;
|
||||
|
||||
if (reuseAutocomplete) {
|
||||
onAutocomplete(mAutoCompleteResult);
|
||||
}
|
||||
}
|
||||
|
||||
// If the edit text has a composition string, don't call updateGoButton().
|
||||
// That method resets IME and composition state will be broken.
|
||||
if (!hasCompositionString(s) ||
|
||||
InputMethods.isGestureKeyboard(mUrlEditText.getContext())) {
|
||||
updateGoButton(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return getScrollY() == 0;
|
||||
return ViewHelper.getTranslationY(this) == 0;
|
||||
}
|
||||
|
||||
public void setNextFocusDownId(int nextId) {
|
||||
@ -875,19 +654,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
return getWidth() - mTabs.getLeft();
|
||||
}
|
||||
|
||||
private static boolean hasCompositionString(Editable content) {
|
||||
Object[] spans = content.getSpans(0, content.length(), Object.class);
|
||||
if (spans != null) {
|
||||
for (Object span : spans) {
|
||||
if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
|
||||
// Found composition string.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canDoBack(Tab tab) {
|
||||
return (tab.canDoBack() && !mIsEditing);
|
||||
}
|
||||
@ -1082,11 +848,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
return;
|
||||
}
|
||||
|
||||
mUrlEditText.setText(suggestion);
|
||||
mUrlEditText.setSelection(mUrlEditText.getText().length());
|
||||
mUrlEditText.requestFocus();
|
||||
|
||||
showSoftInput();
|
||||
mUrlEditLayout.onEditSuggestion(suggestion);
|
||||
}
|
||||
|
||||
public void setTitle(CharSequence title) {
|
||||
@ -1105,7 +867,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
String url = tab.getURL();
|
||||
|
||||
if (!isEditing()) {
|
||||
mUrlEditText.setText(url);
|
||||
mUrlEditLayout.setText(url);
|
||||
}
|
||||
|
||||
// Setting a null title will ensure we just see the "Enter Search or Address" placeholder text.
|
||||
@ -1221,14 +983,17 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
|
||||
public void setOnCommitListener(OnCommitListener listener) {
|
||||
mCommitListener = listener;
|
||||
mUrlEditLayout.setOnCommitListener(listener);
|
||||
}
|
||||
|
||||
public void setOnDismissListener(OnDismissListener listener) {
|
||||
mDismissListener = listener;
|
||||
mUrlEditLayout.setOnDismissListener(listener);
|
||||
}
|
||||
|
||||
public void setOnFilterListener(OnFilterListener listener) {
|
||||
mFilterListener = listener;
|
||||
mUrlEditLayout.setOnFilterListener(listener);
|
||||
}
|
||||
|
||||
public void setOnStartEditingListener(OnStartEditingListener listener) {
|
||||
@ -1239,41 +1004,33 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
mStopEditingListener = listener;
|
||||
}
|
||||
|
||||
private void showSoftInput() {
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mUrlEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
private void showUrlEditLayout() {
|
||||
setUrlEditLayoutVisibility(true, null);
|
||||
}
|
||||
|
||||
private void showUrlEditContainer() {
|
||||
setUrlEditContainerVisibility(true, null);
|
||||
private void showUrlEditLayout(PropertyAnimator animator) {
|
||||
setUrlEditLayoutVisibility(true, animator);
|
||||
}
|
||||
|
||||
private void showUrlEditContainer(PropertyAnimator animator) {
|
||||
setUrlEditContainerVisibility(true, animator);
|
||||
private void hideUrlEditLayout() {
|
||||
setUrlEditLayoutVisibility(false, null);
|
||||
}
|
||||
|
||||
private void hideUrlEditContainer() {
|
||||
setUrlEditContainerVisibility(false, null);
|
||||
private void hideUrlEditLayout(PropertyAnimator animator) {
|
||||
setUrlEditLayoutVisibility(false, animator);
|
||||
}
|
||||
|
||||
private void hideUrlEditContainer(PropertyAnimator animator) {
|
||||
setUrlEditContainerVisibility(false, animator);
|
||||
}
|
||||
private void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
|
||||
final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayContainer);
|
||||
final View viewToHide = (showEditLayout ? mUrlDisplayContainer : mUrlEditLayout);
|
||||
|
||||
private void setUrlEditContainerVisibility(final boolean showEditContainer, PropertyAnimator animator) {
|
||||
final View viewToShow = (showEditContainer ? mUrlEditContainer : mUrlDisplayContainer);
|
||||
final View viewToHide = (showEditContainer ? mUrlDisplayContainer : mUrlEditContainer);
|
||||
if (showEditLayout) {
|
||||
mUrlEditLayout.prepareShowAnimation(animator);
|
||||
}
|
||||
|
||||
if (animator == null) {
|
||||
viewToHide.setVisibility(View.GONE);
|
||||
viewToShow.setVisibility(View.VISIBLE);
|
||||
|
||||
if (showEditContainer) {
|
||||
mUrlEditText.requestFocus();
|
||||
showSoftInput();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1286,26 +1043,16 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
PropertyAnimator.Property.ALPHA,
|
||||
0.0f);
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
viewToShow.setVisibility(View.VISIBLE);
|
||||
|
||||
if (showEditContainer) {
|
||||
ViewHelper.setAlpha(mGo, 0.0f);
|
||||
mUrlEditText.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
viewToHide.setVisibility(View.GONE);
|
||||
ViewHelper.setAlpha(viewToHide, 1.0f);
|
||||
|
||||
if (showEditContainer) {
|
||||
ViewHelper.setAlpha(mGo, 1.0f);
|
||||
showSoftInput();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1345,6 +1092,11 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsEditing(boolean isEditing) {
|
||||
mIsEditing = isEditing;
|
||||
mUrlEditLayout.setEnabled(isEditing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new
|
||||
* tab button). Note that selection state is independent of editing mode.
|
||||
@ -1358,21 +1110,25 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
return;
|
||||
}
|
||||
|
||||
mUrlEditText.setText(url != null ? url : "");
|
||||
mIsEditing = true;
|
||||
mUrlEditLayout.setText(url != null ? url : "");
|
||||
|
||||
setIsEditing(true);
|
||||
updateChildrenForEditing();
|
||||
|
||||
if (mStartEditingListener != null) {
|
||||
mStartEditingListener.onStartEditing();
|
||||
}
|
||||
|
||||
if (mUrlBarRightEdge != null) {
|
||||
mUrlBarRightEdge.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
final int entryTranslation = getUrlBarEntryTranslation();
|
||||
final int curveTranslation = getUrlBarCurveTranslation();
|
||||
|
||||
// This animation doesn't make much sense in a sidebar UI
|
||||
if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) {
|
||||
showUrlEditContainer();
|
||||
showUrlEditLayout();
|
||||
|
||||
if (!HardwareUtils.isTablet()) {
|
||||
if (mUrlBarRightEdge != null) {
|
||||
@ -1430,7 +1186,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
curveTranslation);
|
||||
}
|
||||
|
||||
showUrlEditContainer(animator);
|
||||
showUrlEditLayout(animator);
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
@ -1469,11 +1225,11 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
|
||||
private String stopEditing() {
|
||||
final String url = mUrlEditText.getText().toString();
|
||||
final String url = mUrlEditLayout.getText();
|
||||
if (!isEditing()) {
|
||||
return url;
|
||||
}
|
||||
mIsEditing = false;
|
||||
setIsEditing(false);
|
||||
|
||||
updateChildrenForEditing();
|
||||
|
||||
@ -1482,7 +1238,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
|
||||
if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) {
|
||||
hideUrlEditContainer();
|
||||
hideUrlEditLayout();
|
||||
|
||||
if (!HardwareUtils.isTablet()) {
|
||||
updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
|
||||
@ -1535,7 +1291,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
0);
|
||||
}
|
||||
|
||||
hideUrlEditContainer(contentAnimator);
|
||||
hideUrlEditLayout(contentAnimator);
|
||||
|
||||
contentAnimator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
@ -1544,6 +1300,10 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
if (mUrlBarRightEdge != null) {
|
||||
mUrlBarRightEdge.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
PropertyAnimator buttonsAnimator = new PropertyAnimator(300);
|
||||
|
||||
// Fade toolbar buttons (page actions, stop) after the entry
|
||||
@ -1571,73 +1331,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
return url;
|
||||
}
|
||||
|
||||
private void updateGoButton(String text) {
|
||||
if (text.length() == 0) {
|
||||
mGo.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
mGo.setVisibility(View.VISIBLE);
|
||||
|
||||
if (InputMethods.shouldDisableUrlBarUpdate(mUrlEditText.getContext())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int imageResource = R.drawable.ic_url_bar_go;
|
||||
String contentDescription = mActivity.getString(R.string.go);
|
||||
int imeAction = EditorInfo.IME_ACTION_GO;
|
||||
|
||||
int actionBits = mUrlEditText.getImeOptions() & EditorInfo.IME_MASK_ACTION;
|
||||
if (StringUtils.isSearchQuery(text, actionBits == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
imageResource = R.drawable.ic_url_bar_search;
|
||||
contentDescription = mActivity.getString(R.string.search);
|
||||
imeAction = EditorInfo.IME_ACTION_SEARCH;
|
||||
}
|
||||
|
||||
InputMethodManager imm = InputMethods.getInputMethodManager(mUrlEditText.getContext());
|
||||
if (imm == null) {
|
||||
return;
|
||||
}
|
||||
boolean restartInput = false;
|
||||
if (actionBits != imeAction) {
|
||||
int optionBits = mUrlEditText.getImeOptions() & ~EditorInfo.IME_MASK_ACTION;
|
||||
mUrlEditText.setImeOptions(optionBits | imeAction);
|
||||
|
||||
mDelayRestartInput = (imeAction == EditorInfo.IME_ACTION_GO) &&
|
||||
(InputMethods.shouldDelayUrlBarUpdate(mUrlEditText.getContext()));
|
||||
if (!mDelayRestartInput) {
|
||||
restartInput = true;
|
||||
}
|
||||
} else if (mDelayRestartInput) {
|
||||
// Only call delayed restartInput when actionBits == imeAction
|
||||
// so if there are two restarts in a row, the first restarts will
|
||||
// be discarded and the second restart will be properly delayed
|
||||
mDelayRestartInput = false;
|
||||
restartInput = true;
|
||||
}
|
||||
if (restartInput) {
|
||||
updateKeyboardInputType();
|
||||
imm.restartInput(mUrlEditText);
|
||||
mGo.setImageResource(imageResource);
|
||||
mGo.setContentDescription(contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKeyboardInputType() {
|
||||
// If the user enters a space, then we know they are entering search terms, not a URL.
|
||||
// We can then switch to text mode so,
|
||||
// 1) the IME auto-inserts spaces between words
|
||||
// 2) the IME doesn't reset input keyboard to Latin keyboard.
|
||||
String text = mUrlEditText.getText().toString();
|
||||
int currentInputType = mUrlEditText.getInputType();
|
||||
int newInputType = StringUtils.isSearchQuery(text, false)
|
||||
? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode
|
||||
: (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode
|
||||
if (newInputType != currentInputType) {
|
||||
mUrlEditText.setRawInputType(newInputType);
|
||||
}
|
||||
}
|
||||
|
||||
public void setButtonEnabled(ImageButton button, boolean enabled) {
|
||||
final Drawable drawable = button.getDrawable();
|
||||
if (drawable != null) {
|
||||
@ -1679,7 +1372,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
layoutParams.leftMargin = 0;
|
||||
|
||||
// Do the same on the URL edit container
|
||||
layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams();
|
||||
layoutParams = (ViewGroup.MarginLayoutParams) mUrlEditLayout.getLayoutParams();
|
||||
layoutParams.leftMargin = 0;
|
||||
|
||||
requestLayout();
|
||||
@ -1696,7 +1389,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
(ViewGroup.MarginLayoutParams)mUrlDisplayContainer.getLayoutParams();
|
||||
layoutParams.leftMargin = mUrlBarViewOffset;
|
||||
|
||||
layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams();
|
||||
layoutParams = (ViewGroup.MarginLayoutParams) mUrlEditLayout.getLayoutParams();
|
||||
layoutParams.leftMargin = mUrlBarViewOffset;
|
||||
|
||||
ViewHelper.setTranslationX(mTitle, 0);
|
||||
@ -1791,13 +1484,12 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
updateForwardButton(canDoForward(tab));
|
||||
|
||||
final boolean isPrivate = tab.isPrivate();
|
||||
mUrlBarBackground.setPrivateMode(isPrivate);
|
||||
setPrivateMode(isPrivate);
|
||||
mTabs.setPrivateMode(isPrivate);
|
||||
mTitle.setPrivateMode(isPrivate);
|
||||
mMenu.setPrivateMode(isPrivate);
|
||||
mMenuIcon.setPrivateMode(isPrivate);
|
||||
mUrlEditText.setPrivateMode(isPrivate);
|
||||
mUrlEditLayout.setPrivateMode(isPrivate);
|
||||
|
||||
if (mBack instanceof BackButton)
|
||||
((BackButton) mBack).setPrivateMode(isPrivate);
|
||||
@ -1879,4 +1571,22 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
Drawable drawable = mTheme.getDrawable(this);
|
||||
if (drawable == null)
|
||||
return;
|
||||
|
||||
StateListDrawable stateList = new StateListDrawable();
|
||||
stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.background_private));
|
||||
stateList.addState(EMPTY_STATE_SET, drawable);
|
||||
|
||||
setBackgroundDrawable(stateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.url_bar_bg);
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.toolbar;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.widget.GeckoLinearLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class BrowserToolbarBackground extends GeckoLinearLayout {
|
||||
private final LightweightTheme mTheme;
|
||||
|
||||
public BrowserToolbarBackground(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
final Drawable drawable = mTheme.getDrawable(this);
|
||||
if (drawable == null)
|
||||
return;
|
||||
|
||||
final StateListDrawable stateList = new StateListDrawable();
|
||||
stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.background_private));
|
||||
stateList.addState(EMPTY_STATE_SET, drawable);
|
||||
|
||||
setBackgroundDrawable(stateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.url_bar_bg);
|
||||
}
|
||||
}
|
193
mobile/android/base/toolbar/ToolbarEditLayout.java
Normal file
193
mobile/android/base/toolbar/ToolbarEditLayout.java
Normal file
@ -0,0 +1,193 @@
|
||||
/* -*- 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.toolbar;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
|
||||
import org.mozilla.gecko.toolbar.ToolbarEditText.OnTextTypeChangeListener;
|
||||
import org.mozilla.gecko.toolbar.ToolbarEditText.TextType;
|
||||
import org.mozilla.gecko.widget.GeckoLinearLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
public class ToolbarEditLayout extends GeckoLinearLayout {
|
||||
|
||||
private final ToolbarEditText mEditText;
|
||||
private final ImageButton mGo;
|
||||
|
||||
private OnCommitListener mCommitListener;
|
||||
private OnFocusChangeListener mFocusChangeListener;
|
||||
|
||||
public ToolbarEditLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
setOrientation(HORIZONTAL);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.toolbar_edit_layout, this);
|
||||
mGo = (ImageButton) findViewById(R.id.go);
|
||||
mEditText = (ToolbarEditText) findViewById(R.id.url_edit_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
mGo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mEditText.setOnTextTypeChangeListener(new OnTextTypeChangeListener() {
|
||||
@Override
|
||||
public void onTextTypeChange(ToolbarEditText editText, TextType textType) {
|
||||
updateGoButton(textType);
|
||||
}
|
||||
});
|
||||
|
||||
mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (mFocusChangeListener != null) {
|
||||
mFocusChangeListener.onFocusChange(ToolbarEditLayout.this, hasFocus);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnFocusChangeListener(OnFocusChangeListener listener) {
|
||||
mFocusChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
|
||||
mGo.setEnabled(enabled);
|
||||
mEditText.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
super.setPrivateMode(isPrivate);
|
||||
mEditText.setPrivateMode(isPrivate);
|
||||
}
|
||||
|
||||
private void updateGoButton(TextType textType) {
|
||||
if (textType == TextType.EMPTY) {
|
||||
mGo.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
mGo.setVisibility(View.VISIBLE);
|
||||
|
||||
final int imageResource;
|
||||
final String contentDescription;
|
||||
|
||||
if (textType == TextType.SEARCH_QUERY) {
|
||||
imageResource = R.drawable.ic_url_bar_search;
|
||||
contentDescription = getContext().getString(R.string.search);
|
||||
} else {
|
||||
imageResource = R.drawable.ic_url_bar_go;
|
||||
contentDescription = getContext().getString(R.string.go);
|
||||
}
|
||||
|
||||
mGo.setImageResource(imageResource);
|
||||
mGo.setContentDescription(contentDescription);
|
||||
}
|
||||
|
||||
private void showSoftInput() {
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
void prepareShowAnimation(PropertyAnimator animator) {
|
||||
if (animator == null) {
|
||||
mEditText.requestFocus();
|
||||
showSoftInput();
|
||||
return;
|
||||
}
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
ViewHelper.setAlpha(mGo, 0.0f);
|
||||
mEditText.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
ViewHelper.setAlpha(mGo, 1.0f);
|
||||
showSoftInput();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setOnCommitListener(OnCommitListener listener) {
|
||||
mCommitListener = listener;
|
||||
mEditText.setOnCommitListener(listener);
|
||||
}
|
||||
|
||||
void setOnDismissListener(OnDismissListener listener) {
|
||||
mEditText.setOnDismissListener(listener);
|
||||
}
|
||||
|
||||
void setOnFilterListener(OnFilterListener listener) {
|
||||
mEditText.setOnFilterListener(listener);
|
||||
}
|
||||
|
||||
boolean onKey(int keyCode, KeyEvent event) {
|
||||
final int prevSelStart = mEditText.getSelectionStart();
|
||||
final int prevSelEnd = mEditText.getSelectionEnd();
|
||||
|
||||
// Manually dispatch the key event to the edit text. If selection changed as
|
||||
// a result of the key event, then give focus back to mEditText
|
||||
mEditText.dispatchKeyEvent(event);
|
||||
|
||||
final int curSelStart = mEditText.getSelectionStart();
|
||||
final int curSelEnd = mEditText.getSelectionEnd();
|
||||
|
||||
if (prevSelStart != curSelStart || prevSelEnd != curSelEnd) {
|
||||
mEditText.requestFocusFromTouch();
|
||||
|
||||
// Restore the selection, which gets lost due to the focus switch
|
||||
mEditText.setSelection(curSelStart, curSelEnd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onEditSuggestion(String suggestion) {
|
||||
mEditText.setText(suggestion);
|
||||
mEditText.setSelection(mEditText.getText().length());
|
||||
mEditText.requestFocus();
|
||||
|
||||
showSoftInput();
|
||||
}
|
||||
|
||||
void setText(String text) {
|
||||
mEditText.setText(text);
|
||||
}
|
||||
|
||||
String getText() {
|
||||
return mEditText.getText().toString();
|
||||
}
|
||||
}
|
349
mobile/android/base/toolbar/ToolbarEditText.java
Normal file
349
mobile/android/base/toolbar/ToolbarEditText.java
Normal file
@ -0,0 +1,349 @@
|
||||
/* -*- 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.toolbar;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
|
||||
import org.mozilla.gecko.CustomEditText;
|
||||
import org.mozilla.gecko.CustomEditText.OnKeyPreImeListener;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
public class ToolbarEditText extends CustomEditText
|
||||
implements AutocompleteHandler {
|
||||
|
||||
private static final String LOGTAG = "GeckoToolbarEditText";
|
||||
|
||||
enum TextType {
|
||||
EMPTY,
|
||||
SEARCH_QUERY,
|
||||
URL
|
||||
}
|
||||
|
||||
interface OnTextTypeChangeListener {
|
||||
public void onTextTypeChange(ToolbarEditText editText, TextType textType);
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private TextType mTextType;
|
||||
|
||||
private OnCommitListener mCommitListener;
|
||||
private OnDismissListener mDismissListener;
|
||||
private OnFilterListener mFilterListener;
|
||||
private OnTextTypeChangeListener mTextTypeListener;
|
||||
|
||||
// The previous autocomplete result returned to us
|
||||
private String mAutoCompleteResult = "";
|
||||
|
||||
// The user typed part of the autocomplete result
|
||||
private String mAutoCompletePrefix = null;
|
||||
|
||||
private boolean mDelayRestartInput;
|
||||
|
||||
public ToolbarEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
|
||||
mTextType = TextType.EMPTY;
|
||||
}
|
||||
|
||||
void setOnCommitListener(OnCommitListener listener) {
|
||||
mCommitListener = listener;
|
||||
}
|
||||
|
||||
void setOnDismissListener(OnDismissListener listener) {
|
||||
mDismissListener = listener;
|
||||
}
|
||||
|
||||
void setOnFilterListener(OnFilterListener listener) {
|
||||
mFilterListener = listener;
|
||||
}
|
||||
|
||||
void setOnTextTypeChangeListener(OnTextTypeChangeListener listener) {
|
||||
mTextTypeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
setOnKeyListener(new KeyListener());
|
||||
setOnKeyPreImeListener(new KeyPreImeListener());
|
||||
addTextChangedListener(new TextChangeListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
|
||||
if (gainFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
try {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(LOGTAG, "InputMethodManagerService, why are you throwing"
|
||||
+ " a NullPointerException? See bug 782096", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Return early if we're backspacing through the string, or
|
||||
// have no autocomplete results
|
||||
@Override
|
||||
public final void onAutocomplete(final String result) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = getText().toString();
|
||||
|
||||
if (result == null) {
|
||||
mAutoCompleteResult = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.startsWith(text) || text.equals(result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mAutoCompleteResult = result;
|
||||
getText().append(result.substring(text.length()));
|
||||
setSelection(text.length(), result.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
updateTextTypeFromText(getText().toString());
|
||||
}
|
||||
|
||||
private void updateKeyboardInputType() {
|
||||
// If the user enters a space, then we know they are entering
|
||||
// search terms, not a URL. We can then switch to text mode so,
|
||||
// 1) the IME auto-inserts spaces between words
|
||||
// 2) the IME doesn't reset input keyboard to Latin keyboard.
|
||||
final String text = getText().toString();
|
||||
final int currentInputType = getInputType();
|
||||
|
||||
final int newInputType = StringUtils.isSearchQuery(text, false)
|
||||
? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode
|
||||
: (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode
|
||||
|
||||
if (newInputType != currentInputType) {
|
||||
setRawInputType(newInputType);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasCompositionString(Editable content) {
|
||||
Object[] spans = content.getSpans(0, content.length(), Object.class);
|
||||
|
||||
if (spans != null) {
|
||||
for (Object span : spans) {
|
||||
if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
|
||||
// Found composition string.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setTextType(TextType textType) {
|
||||
mTextType = textType;
|
||||
|
||||
if (mTextTypeListener != null) {
|
||||
mTextTypeListener.onTextTypeChange(this, mTextType);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTextTypeFromText(String text) {
|
||||
if (text.length() == 0) {
|
||||
setTextType(TextType.EMPTY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputMethods.shouldDisableUrlBarUpdate(mContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int actionBits = getImeOptions() & EditorInfo.IME_MASK_ACTION;
|
||||
|
||||
final int imeAction;
|
||||
if (StringUtils.isSearchQuery(text, actionBits == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
imeAction = EditorInfo.IME_ACTION_SEARCH;
|
||||
} else {
|
||||
imeAction = EditorInfo.IME_ACTION_GO;
|
||||
}
|
||||
|
||||
InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
|
||||
if (imm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean restartInput = false;
|
||||
if (actionBits != imeAction) {
|
||||
int optionBits = getImeOptions() & ~EditorInfo.IME_MASK_ACTION;
|
||||
setImeOptions(optionBits | imeAction);
|
||||
|
||||
mDelayRestartInput = (imeAction == EditorInfo.IME_ACTION_GO) &&
|
||||
(InputMethods.shouldDelayUrlBarUpdate(mContext));
|
||||
|
||||
if (!mDelayRestartInput) {
|
||||
restartInput = true;
|
||||
}
|
||||
} else if (mDelayRestartInput) {
|
||||
// Only call delayed restartInput when actionBits == imeAction
|
||||
// so if there are two restarts in a row, the first restarts will
|
||||
// be discarded and the second restart will be properly delayed
|
||||
mDelayRestartInput = false;
|
||||
restartInput = true;
|
||||
}
|
||||
|
||||
if (restartInput) {
|
||||
updateKeyboardInputType();
|
||||
imm.restartInput(ToolbarEditText.this);
|
||||
}
|
||||
|
||||
setTextType(imeAction == EditorInfo.IME_ACTION_GO ?
|
||||
TextType.URL : TextType.SEARCH_QUERY);
|
||||
}
|
||||
|
||||
private class TextChangeListener implements TextWatcher {
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = s.toString();
|
||||
|
||||
boolean useHandler = false;
|
||||
boolean reuseAutocomplete = false;
|
||||
|
||||
if (!hasCompositionString(s) && !StringUtils.isSearchQuery(text, false)) {
|
||||
useHandler = true;
|
||||
|
||||
// If you're hitting backspace (the string is getting smaller
|
||||
// or is unchanged), don't autocomplete.
|
||||
if (mAutoCompletePrefix != null && (mAutoCompletePrefix.length() >= text.length())) {
|
||||
useHandler = false;
|
||||
} else if (mAutoCompleteResult != null && mAutoCompleteResult.startsWith(text)) {
|
||||
// If this text already matches our autocomplete text, autocomplete likely
|
||||
// won't change. Just reuse the old autocomplete value.
|
||||
useHandler = false;
|
||||
reuseAutocomplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the autocomplete text being set, don't run the filter.
|
||||
if (TextUtils.isEmpty(mAutoCompleteResult) || !mAutoCompleteResult.equals(text)) {
|
||||
if (mFilterListener != null) {
|
||||
mFilterListener.onFilter(text, useHandler ? ToolbarEditText.this : null);
|
||||
}
|
||||
|
||||
mAutoCompletePrefix = text;
|
||||
|
||||
if (reuseAutocomplete) {
|
||||
onAutocomplete(mAutoCompleteResult);
|
||||
}
|
||||
}
|
||||
|
||||
// If the edit text has a composition string, don't call updateGoButton().
|
||||
// That method resets IME and composition state will be broken.
|
||||
if (!hasCompositionString(s) || InputMethods.isGestureKeyboard(mContext)) {
|
||||
updateTextTypeFromText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyPreImeListener implements OnKeyPreImeListener {
|
||||
@Override
|
||||
public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) {
|
||||
// We only want to process one event per tap
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
// If the edit text has a composition string, don't submit the text yet.
|
||||
// ENTER is needed to commit the composition string.
|
||||
final Editable content = getText();
|
||||
if (!hasCompositionString(content)) {
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// Drop the virtual keyboard.
|
||||
clearFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyListener implements View.OnKeyListener {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) {
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mCommitListener != null) {
|
||||
mCommitListener.onCommit();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (GamepadUtils.isBackKey(event)) {
|
||||
if (mDismissListener != null) {
|
||||
mDismissListener.onDismiss();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,9 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
%{C++
|
||||
#include "mozilla/DebugOnly.h"
|
||||
%}
|
||||
|
||||
[ptr] native octetPtr(uint8_t);
|
||||
|
||||
@ -78,10 +81,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
|
||||
inline int32_t AsInt32(uint32_t idx) {
|
||||
int32_t v = 0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetInt32(idx, &v);
|
||||
mozilla::DebugOnly<nsresult> rv = GetInt32(idx, &v);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return v;
|
||||
@ -89,10 +89,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
|
||||
inline int64_t AsInt64(uint32_t idx) {
|
||||
int64_t v = 0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetInt64(idx, &v);
|
||||
mozilla::DebugOnly<nsresult> rv = GetInt64(idx, &v);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return v;
|
||||
@ -100,10 +97,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
|
||||
inline double AsDouble(uint32_t idx) {
|
||||
double v = 0.0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetDouble(idx, &v);
|
||||
mozilla::DebugOnly<nsresult> rv = GetDouble(idx, &v);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return v;
|
||||
@ -112,10 +106,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
inline const char* AsSharedUTF8String(uint32_t idx, uint32_t *len) {
|
||||
const char *str = nullptr;
|
||||
*len = 0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetSharedUTF8String(idx, len, &str);
|
||||
mozilla::DebugOnly<nsresult> rv = GetSharedUTF8String(idx, len, &str);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return str;
|
||||
@ -124,10 +115,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
inline const PRUnichar* AsSharedWString(uint32_t idx, uint32_t *len) {
|
||||
const PRUnichar *str = nullptr;
|
||||
*len = 0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetSharedString(idx, len, &str);
|
||||
mozilla::DebugOnly<nsresult> rv = GetSharedString(idx, len, &str);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return str;
|
||||
@ -136,10 +124,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
inline const uint8_t* AsSharedBlob(uint32_t idx, uint32_t *len) {
|
||||
const uint8_t *blob = nullptr;
|
||||
*len = 0;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetSharedBlob(idx, len, &blob);
|
||||
mozilla::DebugOnly<nsresult> rv = GetSharedBlob(idx, len, &blob);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv) || IsNull(idx),
|
||||
"Getting value failed, wrong column index?");
|
||||
return blob;
|
||||
@ -147,10 +132,7 @@ interface mozIStorageValueArray : nsISupports {
|
||||
|
||||
inline bool IsNull(uint32_t idx) {
|
||||
bool b = false;
|
||||
#ifdef DEBUG
|
||||
nsresult rv =
|
||||
#endif
|
||||
GetIsNull(idx, &b);
|
||||
mozilla::DebugOnly<nsresult> rv = GetIsNull(idx, &b);
|
||||
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
|
||||
"Getting value failed, wrong column index?");
|
||||
return b;
|
||||
|
@ -9,10 +9,18 @@ support-files =
|
||||
thumbnails_crash_content_helper.js
|
||||
thumbnails_update.sjs
|
||||
|
||||
[browser_thumbnails_background.js]
|
||||
# Too many intermittent failures (bug 931889)
|
||||
skip-if = os == "linux"
|
||||
[browser_thumbnails_background_crash.js]
|
||||
[browser_thumbnails_bg_basic.js]
|
||||
[browser_thumbnails_bg_queueing.js]
|
||||
[browser_thumbnails_bg_timeout.js]
|
||||
[browser_thumbnails_bg_redirect.js]
|
||||
[browser_thumbnails_bg_destroy_browser.js]
|
||||
[browser_thumbnails_bg_no_cookies_sent.js]
|
||||
[browser_thumbnails_bg_no_cookies_stored.js]
|
||||
[browser_thumbnails_bg_no_auth_prompt.js]
|
||||
[browser_thumbnails_bg_no_alert.js]
|
||||
[browser_thumbnails_bg_no_duplicates.js]
|
||||
[browser_thumbnails_bg_captureIfMissing.js]
|
||||
[browser_thumbnails_bug726727.js]
|
||||
[browser_thumbnails_bug727765.js]
|
||||
[browser_thumbnails_bug818225.js]
|
||||
|
@ -1,417 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_PAGE_URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_background.sjs";
|
||||
|
||||
const imports = {};
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", imports);
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
|
||||
Cu.import("resource://gre/modules/Task.jsm", imports);
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", imports);
|
||||
registerCleanupFunction(function () {
|
||||
imports.BackgroundPageThumbs._destroy();
|
||||
});
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
waitForExplicitFinish();
|
||||
spawnNextTest();
|
||||
}
|
||||
|
||||
function spawnNextTest() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
let nextTest = tests.shift();
|
||||
info("starting sub-test " + nextTest.name);
|
||||
imports.Task.spawn(nextTest).then(spawnNextTest, function onError(err) {
|
||||
ok(false, err);
|
||||
spawnNextTest();
|
||||
});
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
||||
function basic() {
|
||||
let url = "http://www.example.com/";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail should not be cached yet.");
|
||||
|
||||
let capturedURL = yield capture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
|
||||
ok(file.exists(), "Thumbnail should be cached after capture: " + file.path);
|
||||
file.remove(false);
|
||||
},
|
||||
|
||||
function queueing() {
|
||||
let deferred = imports.Promise.defer();
|
||||
let urls = [
|
||||
"http://www.example.com/0",
|
||||
"http://www.example.com/1",
|
||||
// an item that will timeout to ensure timeouts work and we resume.
|
||||
testPageURL({ wait: 2002 }),
|
||||
"http://www.example.com/2",
|
||||
];
|
||||
let files = urls.map(fileForURL);
|
||||
files.forEach(f => ok(!f.exists(), "Thumbnail should not be cached yet."));
|
||||
urls.forEach(function (url) {
|
||||
let isTimeoutTest = url.indexOf("wait") >= 0;
|
||||
imports.BackgroundPageThumbs.capture(url, {
|
||||
timeout: isTimeoutTest ? 100 : 30000,
|
||||
onDone: function onDone(capturedURL) {
|
||||
ok(urls.length > 0, "onDone called, so URLs should still remain");
|
||||
is(capturedURL, urls.shift(),
|
||||
"Captured URL should be currently expected URL (i.e., " +
|
||||
"capture() callbacks should be called in the correct order)");
|
||||
let file = files.shift();
|
||||
if (isTimeoutTest) {
|
||||
ok(!file.exists(),
|
||||
"Thumbnail shouldn't exist for timed out capture: " + file.path);
|
||||
} else {
|
||||
ok(file.exists(),
|
||||
"Thumbnail should be cached after capture: " + file.path);
|
||||
}
|
||||
if (!urls.length)
|
||||
deferred.resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
function timeout() {
|
||||
let deferred = imports.Promise.defer();
|
||||
let url = testPageURL({ wait: 30000 });
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail should not be cached already.");
|
||||
let numCalls = 0;
|
||||
imports.BackgroundPageThumbs.capture(url, {
|
||||
timeout: 0,
|
||||
onDone: function onDone(capturedURL) {
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
is(numCalls++, 0, "onDone should be called only once");
|
||||
ok(!file.exists(),
|
||||
"Capture timed out so thumbnail should not be cached: " + file.path);
|
||||
deferred.resolve();
|
||||
},
|
||||
});
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
function redirect() {
|
||||
let finalURL = "http://example.com/redirected";
|
||||
let originalURL = testPageURL({ redirect: finalURL });
|
||||
|
||||
let originalFile = fileForURL(originalURL);
|
||||
ok(!originalFile.exists(),
|
||||
"Thumbnail file for original URL should not exist yet.");
|
||||
|
||||
let finalFile = fileForURL(finalURL);
|
||||
ok(!finalFile.exists(),
|
||||
"Thumbnail file for final URL should not exist yet.");
|
||||
|
||||
let capturedURL = yield capture(originalURL);
|
||||
is(capturedURL, originalURL,
|
||||
"Captured URL should be URL passed to capture");
|
||||
ok(originalFile.exists(),
|
||||
"Thumbnail for original URL should be cached: " + originalFile.path);
|
||||
ok(finalFile.exists(),
|
||||
"Thumbnail for final URL should be cached: " + finalFile.path);
|
||||
|
||||
originalFile.remove(false);
|
||||
finalFile.remove(false);
|
||||
},
|
||||
|
||||
function destroyBrowserTimeout() {
|
||||
let url1 = "http://example.com/1";
|
||||
let file1 = fileForURL(url1);
|
||||
ok(!file1.exists(), "First file should not exist yet.");
|
||||
|
||||
let url2 = "http://example.com/2";
|
||||
let file2 = fileForURL(url2);
|
||||
ok(!file2.exists(), "Second file should not exist yet.");
|
||||
|
||||
let defaultTimeout = imports.BackgroundPageThumbs._destroyBrowserTimeout;
|
||||
imports.BackgroundPageThumbs._destroyBrowserTimeout = 1000;
|
||||
|
||||
yield capture(url1);
|
||||
ok(file1.exists(), "First file should exist after capture.");
|
||||
file1.remove(false);
|
||||
|
||||
yield wait(2000);
|
||||
is(imports.BackgroundPageThumbs._thumbBrowser, undefined,
|
||||
"Thumb browser should be destroyed after timeout.");
|
||||
imports.BackgroundPageThumbs._destroyBrowserTimeout = defaultTimeout;
|
||||
|
||||
yield capture(url2);
|
||||
ok(file2.exists(), "Second file should exist after capture.");
|
||||
file2.remove(false);
|
||||
|
||||
isnot(imports.BackgroundPageThumbs._thumbBrowser, undefined,
|
||||
"Thumb browser should exist immediately after capture.");
|
||||
},
|
||||
|
||||
function privateBrowsingActive() {
|
||||
let url = "http://example.com/";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let win = yield openPrivateWindow();
|
||||
let capturedURL = yield capture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(file.exists(),
|
||||
"Thumbnail file should be created even when a private window is open.");
|
||||
file.remove(false);
|
||||
|
||||
win.close();
|
||||
},
|
||||
|
||||
function openPrivateWindowDuringCapture() {
|
||||
let url = "http://example.com/";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let deferred = imports.Promise.defer();
|
||||
|
||||
let waitCount = 0;
|
||||
function maybeFinish() {
|
||||
if (++waitCount == 2)
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
imports.BackgroundPageThumbs.capture(url, {
|
||||
onDone: function (capturedURL) {
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(file.exists(),
|
||||
"Thumbnail file should be created even though a private window " +
|
||||
"was opened during the capture.");
|
||||
file.remove(false);
|
||||
maybeFinish();
|
||||
},
|
||||
});
|
||||
|
||||
// Opening the private window at this point relies on a couple of
|
||||
// implementation details: (1) The capture will start immediately and
|
||||
// synchronously (since at this point in the test, the service is
|
||||
// initialized and its queue is empty), and (2) when it starts the capture
|
||||
// registers with the window watcher.
|
||||
openPrivateWindow().then(function (win) {
|
||||
win.close();
|
||||
maybeFinish();
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
function noCookiesSent() {
|
||||
// Visit the test page in the browser and tell it to set a cookie.
|
||||
let url = testPageURL({ setGreenCookie: true });
|
||||
let tab = gBrowser.loadOneTab(url, { inBackground: false });
|
||||
let browser = tab.linkedBrowser;
|
||||
yield onPageLoad(browser);
|
||||
|
||||
// The root element of the page shouldn't be green yet.
|
||||
let greenStr = "rgb(0, 255, 0)";
|
||||
isnot(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
greenStr,
|
||||
"The page shouldn't be green yet.");
|
||||
|
||||
// Cookie should be set now. Reload the page to verify. Its root element
|
||||
// will be green if the cookie's set.
|
||||
browser.reload();
|
||||
yield onPageLoad(browser);
|
||||
is(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
greenStr,
|
||||
"The page should be green now.");
|
||||
|
||||
// Capture the page. Get the image data of the capture and verify it's not
|
||||
// green. (Checking only the first pixel suffices.)
|
||||
yield capture(url);
|
||||
let file = fileForURL(url);
|
||||
ok(file.exists(), "Thumbnail file should exist after capture.");
|
||||
|
||||
let deferred = imports.Promise.defer();
|
||||
retrieveImageDataForURL(url, function ([r, g, b]) {
|
||||
isnot([r, g, b].toString(), [0, 255, 0].toString(),
|
||||
"The captured page should not be green.");
|
||||
gBrowser.removeTab(tab);
|
||||
file.remove(false);
|
||||
deferred.resolve();
|
||||
});
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
// check that if a page captured in the background attempts to set a cookie,
|
||||
// that cookie is not saved for subsequent requests.
|
||||
function noCookiesStored() {
|
||||
let url = testPageURL({ setRedCookie: true });
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not exist before capture.");
|
||||
yield capture(url);
|
||||
ok(file.exists(), "Thumbnail file should exist after capture.");
|
||||
file.remove(false);
|
||||
// now load it up in a browser - it should *not* be red, otherwise the
|
||||
// cookie above was saved.
|
||||
let tab = gBrowser.loadOneTab(url, { inBackground: false });
|
||||
let browser = tab.linkedBrowser;
|
||||
yield onPageLoad(browser);
|
||||
|
||||
// The root element of the page shouldn't be red.
|
||||
let redStr = "rgb(255, 0, 0)";
|
||||
isnot(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
redStr,
|
||||
"The page shouldn't be red.");
|
||||
gBrowser.removeTab(tab);
|
||||
},
|
||||
|
||||
// the following tests attempt to display modal dialogs. The test just
|
||||
// relies on the fact that if the dialog was displayed the test will hang
|
||||
// and timeout. IOW - the tests would pass if the dialogs appear and are
|
||||
// manually closed by the user - so don't do that :) (obviously there is
|
||||
// noone available to do that when run via tbpl etc, so this should be safe,
|
||||
// and it's tricky to use the window-watcher to check a window *does not*
|
||||
// appear - how long should the watcher be active before assuming it's not
|
||||
// going to appear?)
|
||||
function noAuthPrompt() {
|
||||
let url = "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=anyone";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield capture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(file.exists(),
|
||||
"Thumbnail file should exist even though it requires auth.");
|
||||
file.remove(false);
|
||||
},
|
||||
|
||||
function noAlert() {
|
||||
let url = "data:text/html,<script>try { alert('yo!'); } catch (e) {}</script>";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield capture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(file.exists(),
|
||||
"Thumbnail file should exist even though it alerted.");
|
||||
file.remove(false);
|
||||
},
|
||||
|
||||
function noDuplicates() {
|
||||
let deferred = imports.Promise.defer();
|
||||
let url = "http://example.com/1";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
let numCallbacks = 0;
|
||||
let doneCallback = function(doneUrl) {
|
||||
is(doneUrl, url, "called back with correct url");
|
||||
numCallbacks += 1;
|
||||
// We will delete the file after the first callback, then check it
|
||||
// still doesn't exist on the second callback, which should give us
|
||||
// confidence that we didn't end up with 2 different captures happening
|
||||
// for the same url...
|
||||
if (numCallbacks == 1) {
|
||||
ok(file.exists(), "Thumbnail file should now exist.");
|
||||
file.remove(false);
|
||||
return;
|
||||
}
|
||||
if (numCallbacks == 2) {
|
||||
ok(!file.exists(), "Thumbnail file should still be deleted.");
|
||||
// and that's all we expect, so we are done...
|
||||
deferred.resolve();
|
||||
return;
|
||||
}
|
||||
ok(false, "only expecting 2 callbacks");
|
||||
}
|
||||
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
function capIfMissing() {
|
||||
let url = "http://example.com/";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield captureIfMissing(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
ok(file.exists(), "Thumbnail should be cached after capture: " + file.path);
|
||||
|
||||
let past = Date.now() - 1000000000;
|
||||
let pastFudge = past + 30000;
|
||||
file.lastModifiedTime = past;
|
||||
ok(file.lastModifiedTime < pastFudge, "Last modified time should stick!");
|
||||
capturedURL = yield captureIfMissing(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to second capture");
|
||||
ok(file.exists(), "Thumbnail should remain cached after second capture: " +
|
||||
file.path);
|
||||
ok(file.lastModifiedTime < pastFudge,
|
||||
"File should not have been overwritten");
|
||||
|
||||
file.remove(false);
|
||||
},
|
||||
];
|
||||
|
||||
function capture(url, options) {
|
||||
return captureWithMethod("capture", url, options);
|
||||
}
|
||||
|
||||
function captureIfMissing(url, options) {
|
||||
return captureWithMethod("captureIfMissing", url, options);
|
||||
}
|
||||
|
||||
function captureWithMethod(methodName, url, options={}) {
|
||||
let deferred = imports.Promise.defer();
|
||||
options.onDone = function onDone(capturedURL) {
|
||||
deferred.resolve(capturedURL);
|
||||
};
|
||||
imports.BackgroundPageThumbs[methodName](url, options);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testPageURL(opts) {
|
||||
return TEST_PAGE_URL + "?" + encodeURIComponent(JSON.stringify(opts || {}));
|
||||
}
|
||||
|
||||
function fileForURL(url) {
|
||||
let path = imports.PageThumbsStorage.getFilePathForURL(url);
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(path);
|
||||
return file;
|
||||
}
|
||||
|
||||
function wait(ms) {
|
||||
let deferred = imports.Promise.defer();
|
||||
setTimeout(function onTimeout() {
|
||||
deferred.resolve();
|
||||
}, ms);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function openPrivateWindow() {
|
||||
let deferred = imports.Promise.defer();
|
||||
// from OpenBrowserWindow in browser.js
|
||||
let win = window.openDialog("chrome://browser/content/", "_blank",
|
||||
"chrome,all,dialog=no,private",
|
||||
"about:privatebrowsing");
|
||||
win.addEventListener("load", function load(event) {
|
||||
if (event.target == win.document) {
|
||||
win.removeEventListener("load", load);
|
||||
deferred.resolve(win);
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function onPageLoad(browser) {
|
||||
let deferred = imports.Promise.defer();
|
||||
browser.addEventListener("load", function load(event) {
|
||||
if (event.target == browser.contentWindow.document) {
|
||||
browser.removeEventListener("load", load, true);
|
||||
deferred.resolve();
|
||||
}
|
||||
}, true);
|
||||
return deferred.promise;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url = "http://www.example.com/";
|
||||
ok(!thumbnailExists(url), "Thumbnail should not be cached yet.");
|
||||
|
||||
let capturedURL = yield bgCapture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
|
||||
ok(thumbnailExists(url), "Thumbnail should be cached after capture");
|
||||
removeThumbnail(url);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url = "http://example.com/";
|
||||
let file = thumbnailFile(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield bgCaptureIfMissing(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
ok(file.exists(url), "Thumbnail should be cached after capture");
|
||||
|
||||
let past = Date.now() - 1000000000;
|
||||
let pastFudge = past + 30000;
|
||||
file.lastModifiedTime = past;
|
||||
ok(file.lastModifiedTime < pastFudge, "Last modified time should stick!");
|
||||
capturedURL = yield bgCaptureIfMissing(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to second capture");
|
||||
ok(file.exists(), "Thumbnail should remain cached after second capture");
|
||||
ok(file.lastModifiedTime < pastFudge,
|
||||
"File should not have been overwritten");
|
||||
|
||||
file.remove(false);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url1 = "http://example.com/1";
|
||||
ok(!thumbnailExists(url1), "First file should not exist yet.");
|
||||
|
||||
let url2 = "http://example.com/2";
|
||||
ok(!thumbnailExists(url2), "Second file should not exist yet.");
|
||||
|
||||
let defaultTimeout = BackgroundPageThumbs._destroyBrowserTimeout;
|
||||
BackgroundPageThumbs._destroyBrowserTimeout = 1000;
|
||||
|
||||
yield bgCapture(url1);
|
||||
ok(thumbnailExists(url1), "First file should exist after capture.");
|
||||
removeThumbnail(url1);
|
||||
|
||||
yield wait(2000);
|
||||
is(BackgroundPageThumbs._thumbBrowser, undefined,
|
||||
"Thumb browser should be destroyed after timeout.");
|
||||
BackgroundPageThumbs._destroyBrowserTimeout = defaultTimeout;
|
||||
|
||||
yield bgCapture(url2);
|
||||
ok(thumbnailExists(url2), "Second file should exist after capture.");
|
||||
removeThumbnail(url2);
|
||||
|
||||
isnot(BackgroundPageThumbs._thumbBrowser, undefined,
|
||||
"Thumb browser should exist immediately after capture.");
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url = "data:text/html,<script>try { alert('yo!'); } catch (e) {}</script>";
|
||||
ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield bgCapture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(thumbnailExists(url),
|
||||
"Thumbnail file should exist even though it alerted.");
|
||||
removeThumbnail(url);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// the following tests attempt to display modal dialogs. The test just
|
||||
// relies on the fact that if the dialog was displayed the test will hang
|
||||
// and timeout. IOW - the tests would pass if the dialogs appear and are
|
||||
// manually closed by the user - so don't do that :) (obviously there is
|
||||
// noone available to do that when run via tbpl etc, so this should be safe,
|
||||
// and it's tricky to use the window-watcher to check a window *does not*
|
||||
// appear - how long should the watcher be active before assuming it's not
|
||||
// going to appear?)
|
||||
function runTests() {
|
||||
let url = "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=anyone";
|
||||
ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
|
||||
|
||||
let capturedURL = yield bgCapture(url);
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(thumbnailExists(url),
|
||||
"Thumbnail file should exist even though it requires auth.");
|
||||
removeThumbnail(url);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
// Visit the test page in the browser and tell it to set a cookie.
|
||||
let url = bgTestPageURL({ setGreenCookie: true });
|
||||
let tab = gBrowser.loadOneTab(url, { inBackground: false });
|
||||
let browser = tab.linkedBrowser;
|
||||
yield whenLoaded(browser);
|
||||
|
||||
// The root element of the page shouldn't be green yet.
|
||||
let greenStr = "rgb(0, 255, 0)";
|
||||
isnot(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
greenStr,
|
||||
"The page shouldn't be green yet.");
|
||||
|
||||
// Cookie should be set now. Reload the page to verify. Its root element
|
||||
// will be green if the cookie's set.
|
||||
browser.reload();
|
||||
yield whenLoaded(browser);
|
||||
is(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
greenStr,
|
||||
"The page should be green now.");
|
||||
|
||||
// Capture the page. Get the image data of the capture and verify it's not
|
||||
// green. (Checking only the first pixel suffices.)
|
||||
yield bgCapture(url);
|
||||
ok(thumbnailExists(url), "Thumbnail file should exist after capture.");
|
||||
|
||||
retrieveImageDataForURL(url, function ([r, g, b]) {
|
||||
isnot([r, g, b].toString(), [0, 255, 0].toString(),
|
||||
"The captured page should not be green.");
|
||||
gBrowser.removeTab(tab);
|
||||
removeThumbnail(url);
|
||||
next();
|
||||
});
|
||||
yield true;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// check that if a page captured in the background attempts to set a cookie,
|
||||
// that cookie is not saved for subsequent requests.
|
||||
function runTests() {
|
||||
let url = bgTestPageURL({ setRedCookie: true });
|
||||
ok(!thumbnailExists(url), "Thumbnail file should not exist before capture.");
|
||||
yield bgCapture(url);
|
||||
ok(thumbnailExists(url), "Thumbnail file should exist after capture.");
|
||||
removeThumbnail(url);
|
||||
// now load it up in a browser - it should *not* be red, otherwise the
|
||||
// cookie above was saved.
|
||||
let tab = gBrowser.loadOneTab(url, { inBackground: false });
|
||||
let browser = tab.linkedBrowser;
|
||||
yield whenLoaded(browser);
|
||||
|
||||
// The root element of the page shouldn't be red.
|
||||
let redStr = "rgb(255, 0, 0)";
|
||||
isnot(browser.contentDocument.documentElement.style.backgroundColor,
|
||||
redStr,
|
||||
"The page shouldn't be red.");
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url = "http://example.com/1";
|
||||
ok(!thumbnailExists(url), "Thumbnail file should not already exist.");
|
||||
let numCallbacks = 0;
|
||||
let doneCallback = function(doneUrl) {
|
||||
is(doneUrl, url, "called back with correct url");
|
||||
numCallbacks += 1;
|
||||
// We will delete the file after the first callback, then check it
|
||||
// still doesn't exist on the second callback, which should give us
|
||||
// confidence that we didn't end up with 2 different captures happening
|
||||
// for the same url...
|
||||
if (numCallbacks == 1) {
|
||||
ok(thumbnailExists(url), "Thumbnail file should now exist.");
|
||||
removeThumbnail(url);
|
||||
return;
|
||||
}
|
||||
if (numCallbacks == 2) {
|
||||
ok(!thumbnailExists(url), "Thumbnail file should still be deleted.");
|
||||
// and that's all we expect, so we are done...
|
||||
next();
|
||||
return;
|
||||
}
|
||||
ok(false, "only expecting 2 callbacks");
|
||||
}
|
||||
BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
yield true;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let urls = [
|
||||
"http://www.example.com/0",
|
||||
"http://www.example.com/1",
|
||||
// an item that will timeout to ensure timeouts work and we resume.
|
||||
bgTestPageURL({ wait: 2002 }),
|
||||
"http://www.example.com/2",
|
||||
];
|
||||
urls.forEach(url => {
|
||||
ok(!thumbnailExists(url), "Thumbnail should not exist yet: " + url);
|
||||
let isTimeoutTest = url.indexOf("wait") >= 0;
|
||||
BackgroundPageThumbs.capture(url, {
|
||||
timeout: isTimeoutTest ? 100 : 30000,
|
||||
onDone: function onDone(capturedURL) {
|
||||
ok(urls.length > 0, "onDone called, so URLs should still remain");
|
||||
is(capturedURL, urls.shift(),
|
||||
"Captured URL should be currently expected URL (i.e., " +
|
||||
"capture() callbacks should be called in the correct order)");
|
||||
if (isTimeoutTest) {
|
||||
ok(!thumbnailExists(capturedURL),
|
||||
"Thumbnail shouldn't exist for timed out capture");
|
||||
} else {
|
||||
ok(thumbnailExists(capturedURL),
|
||||
"Thumbnail should be cached after capture");
|
||||
removeThumbnail(url);
|
||||
}
|
||||
if (!urls.length)
|
||||
// Test done.
|
||||
next();
|
||||
},
|
||||
});
|
||||
});
|
||||
yield true;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let finalURL = "http://example.com/redirected";
|
||||
let originalURL = bgTestPageURL({ redirect: finalURL });
|
||||
|
||||
ok(!thumbnailExists(originalURL),
|
||||
"Thumbnail file for original URL should not exist yet.");
|
||||
ok(!thumbnailExists(finalURL),
|
||||
"Thumbnail file for final URL should not exist yet.");
|
||||
|
||||
let capturedURL = yield bgCapture(originalURL);
|
||||
is(capturedURL, originalURL,
|
||||
"Captured URL should be URL passed to capture");
|
||||
ok(thumbnailExists(originalURL),
|
||||
"Thumbnail for original URL should be cached");
|
||||
ok(thumbnailExists(finalURL),
|
||||
"Thumbnail for final URL should be cached");
|
||||
|
||||
removeThumbnail(originalURL);
|
||||
removeThumbnail(finalURL);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
let url = bgTestPageURL({ wait: 30000 });
|
||||
ok(!thumbnailExists(url), "Thumbnail should not be cached already.");
|
||||
let numCalls = 0;
|
||||
BackgroundPageThumbs.capture(url, {
|
||||
timeout: 0,
|
||||
onDone: function onDone(capturedURL) {
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture");
|
||||
is(numCalls++, 0, "onDone should be called only once");
|
||||
ok(!thumbnailExists(url),
|
||||
"Capture timed out so thumbnail should not be cached");
|
||||
next();
|
||||
},
|
||||
});
|
||||
yield true;
|
||||
}
|
@ -3,10 +3,11 @@
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", tmp);
|
||||
Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/osfile.jsm", tmp);
|
||||
let {PageThumbs, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
|
||||
let {PageThumbs, BackgroundPageThumbs, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
|
||||
|
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
@ -48,10 +49,12 @@ let TestRunner = {
|
||||
|
||||
/**
|
||||
* Runs the next available test or finishes if there's no test left.
|
||||
* @param aValue This value will be passed to the yielder via the runner's
|
||||
* iterator.
|
||||
*/
|
||||
next: function () {
|
||||
next: function (aValue) {
|
||||
try {
|
||||
let value = TestRunner._iter.next();
|
||||
let value = TestRunner._iter.send(aValue);
|
||||
if (value && typeof value.then == "function") {
|
||||
value.then(result => {
|
||||
next(result);
|
||||
@ -67,9 +70,11 @@ let TestRunner = {
|
||||
|
||||
/**
|
||||
* Continues the current test execution.
|
||||
* @param aValue This value will be passed to the yielder via the runner's
|
||||
* iterator.
|
||||
*/
|
||||
function next() {
|
||||
TestRunner.next();
|
||||
function next(aValue) {
|
||||
TestRunner.next(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,15 +169,32 @@ function retrieveImageDataForURL(aURL, aCallback) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file of the thumbnail with the given URL.
|
||||
* @param aURL The URL of the thumbnail.
|
||||
*/
|
||||
function thumbnailFile(aURL) {
|
||||
return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a thumbnail for the given URL exists.
|
||||
* @param aURL The url associated to the thumbnail.
|
||||
*/
|
||||
function thumbnailExists(aURL) {
|
||||
let file = new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
|
||||
let file = thumbnailFile(aURL);
|
||||
return file.exists() && file.fileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the thumbnail for the given URL.
|
||||
* @param aURL The URL associated with the thumbnail.
|
||||
*/
|
||||
function removeThumbnail(aURL) {
|
||||
let file = thumbnailFile(aURL);
|
||||
file.remove(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously adds visits to a page, invoking a callback function when done.
|
||||
*
|
||||
@ -263,6 +285,10 @@ function whenFileRemoved(aFile, aCallback) {
|
||||
executeSoon(callback || next);
|
||||
}
|
||||
|
||||
function wait(aMillis) {
|
||||
setTimeout(next, aMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that a given list of URLs is not implicitly expired.
|
||||
*
|
||||
@ -276,3 +302,21 @@ function dontExpireThumbnailURLs(aURLs) {
|
||||
PageThumbs.removeExpirationFilter(dontExpireURLs);
|
||||
});
|
||||
}
|
||||
|
||||
function bgCapture(aURL, aOptions) {
|
||||
bgCaptureWithMethod("capture", aURL, aOptions);
|
||||
}
|
||||
|
||||
function bgCaptureIfMissing(aURL, aOptions) {
|
||||
bgCaptureWithMethod("captureIfMissing", aURL, aOptions);
|
||||
}
|
||||
|
||||
function bgCaptureWithMethod(aMethodName, aURL, aOptions = {}) {
|
||||
aOptions.onDone = next;
|
||||
BackgroundPageThumbs[aMethodName](aURL, aOptions);
|
||||
}
|
||||
|
||||
function bgTestPageURL(aOpts = {}) {
|
||||
let TEST_PAGE_URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_background.sjs";
|
||||
return TEST_PAGE_URL + "?" + encodeURIComponent(JSON.stringify(aOpts));
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ function onFinish()
|
||||
|
||||
// Create profile named profileName in profileRoot.
|
||||
try {
|
||||
profile = gProfileService.createProfile(gProfileRoot, null, profileName);
|
||||
profile = gProfileService.createProfile(gProfileRoot, profileName);
|
||||
}
|
||||
catch (e) {
|
||||
var profileCreationFailed =
|
||||
|
@ -10,7 +10,7 @@ interface nsIFile;
|
||||
interface nsIToolkitProfile;
|
||||
interface nsIProfileLock;
|
||||
|
||||
[scriptable, uuid(b619f83d-8317-473c-b342-67905993fdc7)]
|
||||
[scriptable, uuid(b81c33a6-1ce8-4695-856b-02b7f15cc114)]
|
||||
interface nsIToolkitProfileService : nsISupports
|
||||
{
|
||||
attribute boolean startWithLastProfile;
|
||||
@ -37,19 +37,17 @@ interface nsIToolkitProfileService : nsISupports
|
||||
|
||||
/**
|
||||
* Create a new profile.
|
||||
*
|
||||
* The profile temporary directory will be chosen based on where the
|
||||
* profile directory is located.
|
||||
*
|
||||
* @param aRootDir
|
||||
* The profile directory. May be null, in which case a suitable
|
||||
* default will be chosen based on the profile name.
|
||||
* @param aTempDir
|
||||
* The profile temporary directory. May be null, in which case a
|
||||
* suitable default will be chosen based either on the profile name
|
||||
* if aRootDir is null or aRootDir itself.
|
||||
* @param aName
|
||||
* The profile name.
|
||||
*/
|
||||
nsIToolkitProfile createProfile(in nsIFile aRootDir,
|
||||
in nsIFile aTempDir,
|
||||
in AUTF8String aName);
|
||||
|
||||
/**
|
||||
|
@ -127,7 +127,6 @@ private:
|
||||
nsresult CreateTimesInternal(nsIFile *profileDir);
|
||||
|
||||
nsresult CreateProfileInternal(nsIFile* aRootDir,
|
||||
nsIFile* aLocalDir,
|
||||
const nsACString& aName,
|
||||
const nsACString* aProfileName,
|
||||
const nsACString* aAppName,
|
||||
@ -656,7 +655,7 @@ nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileNa
|
||||
NS_ENSURE_FALSE(exists, NS_ERROR_ALREADY_INITIALIZED);
|
||||
|
||||
nsIFile* profileDefaultsDir = aProfileDefaultsDir;
|
||||
rv = CreateProfileInternal(nullptr, nullptr,
|
||||
rv = CreateProfileInternal(nullptr,
|
||||
NS_LITERAL_CSTRING("default"),
|
||||
&aProfileName, &aAppName, &aVendorName,
|
||||
&profileDefaultsDir, true, aResult);
|
||||
@ -698,17 +697,15 @@ nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileNa
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
|
||||
nsIFile* aLocalDir,
|
||||
const nsACString& aName,
|
||||
nsIToolkitProfile** aResult)
|
||||
{
|
||||
return CreateProfileInternal(aRootDir, aLocalDir, aName,
|
||||
return CreateProfileInternal(aRootDir, aName,
|
||||
nullptr, nullptr, nullptr, nullptr, false, aResult);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
|
||||
nsIFile* aLocalDir,
|
||||
const nsACString& aName,
|
||||
const nsACString* aProfileName,
|
||||
const nsACString* aAppName,
|
||||
@ -745,26 +742,22 @@ nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> localDir (aLocalDir);
|
||||
nsCOMPtr<nsIFile> localDir;
|
||||
|
||||
if (!localDir) {
|
||||
if (aRootDir) {
|
||||
localDir = aRootDir;
|
||||
}
|
||||
else {
|
||||
rv = gDirServiceProvider->GetUserProfilesLocalDir(getter_AddRefs(localDir),
|
||||
aProfileName,
|
||||
aAppName,
|
||||
aVendorName);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
bool isRelative;
|
||||
rv = mAppData->Contains(rootDir, true, &isRelative);
|
||||
if (NS_SUCCEEDED(rv) && isRelative) {
|
||||
nsAutoCString path;
|
||||
rv = rootDir->GetRelativeDescriptor(mAppData, path);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// use same salting
|
||||
if (NS_IsNativeUTF8()) {
|
||||
localDir->AppendNative(dirName);
|
||||
} else {
|
||||
localDir->Append(NS_ConvertUTF8toUTF16(dirName));
|
||||
}
|
||||
}
|
||||
rv = NS_NewNativeLocalFile(EmptyCString(), true,
|
||||
getter_AddRefs(localDir));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = localDir->SetRelativeDescriptor(mTempData, path);
|
||||
} else {
|
||||
localDir = rootDir;
|
||||
}
|
||||
|
||||
bool exists;
|
||||
|
@ -29,15 +29,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=543854
|
||||
const ASCIIName = "myprofile";
|
||||
const UnicodeName = "\u09A0\u09BE\u0995\u09C1\u09B0"; // A Bengali name
|
||||
|
||||
var gDirService;
|
||||
var gIOService;
|
||||
var gProfileService;
|
||||
|
||||
var gDefaultLocalProfileParent;
|
||||
|
||||
gDirService = Cc["@mozilla.org/file/directory_service;1"].
|
||||
getService(Ci.nsIProperties);
|
||||
|
||||
gIOService = Cc["@mozilla.org/network/io-service;1"].
|
||||
getService(Ci.nsIIOService);
|
||||
|
||||
gProfileService = Cc["@mozilla.org/toolkit/profile-service;1"].
|
||||
getService(Ci.nsIToolkitProfileService);
|
||||
|
||||
gDefaultLocalProfileParent = gDirService.get("DefProfLRt", Ci.nsIFile);
|
||||
|
||||
createProfile(ASCIIName);
|
||||
createProfile(UnicodeName);
|
||||
SimpleTest.finish();
|
||||
@ -76,7 +84,7 @@ function createProfile(profileName) {
|
||||
// Filesystem precision is lower than Date precision.
|
||||
let lowerBound = Date.now() - 1000;
|
||||
|
||||
let profile = gProfileService.createProfile(null, null, profileName);
|
||||
let profile = gProfileService.createProfile(null, profileName);
|
||||
|
||||
// check that the directory was created
|
||||
isnot(profile, null, "Profile " + profileName + " created");
|
||||
@ -107,6 +115,16 @@ function createProfile(profileName) {
|
||||
// Check against real clock time.
|
||||
checkBounds(lowerBound, created, upperBound);
|
||||
|
||||
// Clean up the profile before local profile test.
|
||||
profile.remove(true);
|
||||
|
||||
// Create with non-null aRootDir
|
||||
let profile = gProfileService.createProfile(profileDir, profileName);
|
||||
|
||||
let localProfileDir = profile.localDir;
|
||||
ok(gDefaultLocalProfileParent.contains(localProfileDir, false),
|
||||
"Local profile dir created in DefProfLRt");
|
||||
|
||||
// Clean up the profile.
|
||||
profile.remove(true);
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ CreateResetProfile(nsIToolkitProfileService* aProfileSvc, nsIToolkitProfile* *aN
|
||||
nsAutoCString newProfileName("default-");
|
||||
newProfileName.Append(nsPrintfCString("%lld", PR_Now() / 1000));
|
||||
nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us
|
||||
nullptr, // choose a default dir for us
|
||||
newProfileName,
|
||||
getter_AddRefs(newProfile));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
@ -2185,12 +2185,12 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n
|
||||
return rv;
|
||||
}
|
||||
|
||||
// As with -profile, assume that the given path will be used for both the
|
||||
// main profile directory and the temp profile directory.
|
||||
rv = aProfileSvc->CreateProfile(lf, lf, nsDependentCSubstring(arg, delim),
|
||||
// As with -profile, assume that the given path will be used for the
|
||||
// main profile directory.
|
||||
rv = aProfileSvc->CreateProfile(lf, nsDependentCSubstring(arg, delim),
|
||||
getter_AddRefs(profile));
|
||||
} else {
|
||||
rv = aProfileSvc->CreateProfile(nullptr, nullptr, nsDependentCString(arg),
|
||||
rv = aProfileSvc->CreateProfile(nullptr, nsDependentCString(arg),
|
||||
getter_AddRefs(profile));
|
||||
}
|
||||
// Some pathological arguments can make it this far
|
||||
@ -2275,7 +2275,6 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n
|
||||
// create a default profile
|
||||
nsCOMPtr<nsIToolkitProfile> profile;
|
||||
nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us
|
||||
nullptr, // choose a default dir for us
|
||||
NS_LITERAL_CSTRING("default"),
|
||||
getter_AddRefs(profile));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
|
Loading…
Reference in New Issue
Block a user