Merge m-c to b2g-inbound.
|
@ -163,8 +163,7 @@
|
||||||
let findBar = document.createElementNS(this.namespaceURI, "findbar");
|
let findBar = document.createElementNS(this.namespaceURI, "findbar");
|
||||||
let browser = this.getBrowserForTab(aTab);
|
let browser = this.getBrowserForTab(aTab);
|
||||||
let browserContainer = this.getBrowserContainer(browser);
|
let browserContainer = this.getBrowserContainer(browser);
|
||||||
findBar.setAttribute("position", "top");
|
browserContainer.appendChild(findBar);
|
||||||
browserContainer.insertBefore(findBar, browserContainer.firstChild);
|
|
||||||
|
|
||||||
// Force a style flush to ensure that our binding is attached.
|
// Force a style flush to ensure that our binding is attached.
|
||||||
findBar.clientTop;
|
findBar.clientTop;
|
||||||
|
|
|
@ -259,23 +259,63 @@ let UI = {
|
||||||
debug: function(button, location) {
|
debug: function(button, location) {
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
let project = AppProjects.get(location);
|
let project = AppProjects.get(location);
|
||||||
|
|
||||||
|
let onFailedToStart = (error) => {
|
||||||
|
// If not installed, install and open it
|
||||||
|
if (error == "NO_SUCH_APP") {
|
||||||
|
return this.install(project);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let onStarted = () => {
|
||||||
|
// Once we asked the app to launch, the app isn't necessary completely loaded.
|
||||||
|
// launch request only ask the app to launch and immediatly returns.
|
||||||
|
// We have to keep trying to get app tab actors required to create its target.
|
||||||
|
let deferred = promise.defer();
|
||||||
|
let loop = (count) => {
|
||||||
|
// Ensure not looping for ever
|
||||||
|
if (count >= 100) {
|
||||||
|
deferred.reject("Unable to connect to the app");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Also, in case the app wasn't installed yet, we also have to keep asking the
|
||||||
|
// app to launch, as launch request made right after install may race.
|
||||||
|
this.start(project);
|
||||||
|
getTargetForApp(
|
||||||
|
this.connection.client,
|
||||||
|
this.listTabsResponse.webappsActor,
|
||||||
|
this._getProjectManifestURL(project)).
|
||||||
|
then(deferred.resolve,
|
||||||
|
(err) => {
|
||||||
|
if (err == "appNotFound")
|
||||||
|
setTimeout(loop, 500, count + 1);
|
||||||
|
else
|
||||||
|
deferred.reject(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
loop(0);
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
let onTargetReady = (target) => {
|
||||||
|
// Finally, when it's finally opened, display the toolbox
|
||||||
|
let deferred = promise.defer();
|
||||||
|
gDevTools.showToolbox(target,
|
||||||
|
null,
|
||||||
|
devtools.Toolbox.HostType.WINDOW).then(toolbox => {
|
||||||
|
this.connection.once(Connection.Events.DISCONNECTED, () => {
|
||||||
|
toolbox.destroy();
|
||||||
|
});
|
||||||
|
deferred.resolve(toolbox);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
// First try to open the app
|
// First try to open the app
|
||||||
this.start(project)
|
this.start(project)
|
||||||
.then(
|
.then(null, onFailedToStart)
|
||||||
null,
|
.then(onStarted)
|
||||||
(error) => {
|
.then(onTargetReady)
|
||||||
// If not installed, install and open it
|
|
||||||
if (error == "NO_SUCH_APP") {
|
|
||||||
return this.install(project)
|
|
||||||
.then(() => this.start(project));
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// Finally, when it's finally opened, display the toolbox
|
|
||||||
return this.openToolbox(project)
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// And only when the toolbox is opened, release the button
|
// And only when the toolbox is opened, release the button
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
|
@ -287,24 +327,6 @@ let UI = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openToolbox: function(project) {
|
|
||||||
let deferred = promise.defer();
|
|
||||||
let manifest = this._getProjectManifestURL(project);
|
|
||||||
getTargetForApp(this.connection.client,
|
|
||||||
this.listTabsResponse.webappsActor,
|
|
||||||
manifest).then((target) => {
|
|
||||||
gDevTools.showToolbox(target,
|
|
||||||
null,
|
|
||||||
devtools.Toolbox.HostType.WINDOW).then(toolbox => {
|
|
||||||
this.connection.once(Connection.Events.DISCONNECTED, () => {
|
|
||||||
toolbox.destroy();
|
|
||||||
});
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
}, deferred.reject);
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
reveal: function(location) {
|
reveal: function(location) {
|
||||||
let project = AppProjects.get(location);
|
let project = AppProjects.get(location);
|
||||||
if (project.type == "packaged") {
|
if (project.type == "packaged") {
|
||||||
|
|
|
@ -1864,8 +1864,11 @@ NetworkDetailsView.prototype = {
|
||||||
let { mimeType, text, encoding } = aResponse.content;
|
let { mimeType, text, encoding } = aResponse.content;
|
||||||
|
|
||||||
gNetwork.getString(text).then(aString => {
|
gNetwork.getString(text).then(aString => {
|
||||||
// Handle json.
|
// Handle json, which we tentatively identify by checking the MIME type
|
||||||
if (mimeType.contains("/json")) {
|
// for "json" after any word boundary. This works for the standard
|
||||||
|
// "application/json", and also for custom types like "x-bigcorp-json".
|
||||||
|
// This should be marginally more reliable than just looking for "json".
|
||||||
|
if (/\bjson/.test(mimeType)) {
|
||||||
let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; // JSONP with callback.
|
let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; // JSONP with callback.
|
||||||
let sanitizedJSON = aString.replace(jsonpRegex, "");
|
let sanitizedJSON = aString.replace(jsonpRegex, "");
|
||||||
let callbackPadding = aString.match(jsonpRegex);
|
let callbackPadding = aString.match(jsonpRegex);
|
||||||
|
|
|
@ -24,6 +24,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||||
browser_net_jsonp.js \
|
browser_net_jsonp.js \
|
||||||
browser_net_json-long.js \
|
browser_net_json-long.js \
|
||||||
browser_net_json-malformed.js \
|
browser_net_json-malformed.js \
|
||||||
|
browser_net_json_custom_mime.js \
|
||||||
browser_net_timeline_ticks.js \
|
browser_net_timeline_ticks.js \
|
||||||
browser_net_sort-01.js \
|
browser_net_sort-01.js \
|
||||||
browser_net_sort-02.js \
|
browser_net_sort-02.js \
|
||||||
|
@ -50,6 +51,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||||
html_jsonp-test-page.html \
|
html_jsonp-test-page.html \
|
||||||
html_json-long-test-page.html \
|
html_json-long-test-page.html \
|
||||||
html_json-malformed-test-page.html \
|
html_json-malformed-test-page.html \
|
||||||
|
html_json-custom-mime-test-page.html \
|
||||||
html_sorting-test-page.html \
|
html_sorting-test-page.html \
|
||||||
html_filter-test-page.html \
|
html_filter-test-page.html \
|
||||||
html_infinite-get-page.html \
|
html_infinite-get-page.html \
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if JSON responses with unusal/custom MIME types are handled correctly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(JSON_CUSTOM_MIME_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin;
|
||||||
|
let { RequestsMenu } = NetMonitorView;
|
||||||
|
|
||||||
|
RequestsMenu.lazyUpdate = false;
|
||||||
|
|
||||||
|
waitForNetworkEvents(aMonitor, 1).then(() => {
|
||||||
|
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||||
|
"GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
type: "x-bigcorp-json",
|
||||||
|
fullMimeType: "text/x-bigcorp-json; charset=utf-8",
|
||||||
|
size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
|
||||||
|
time: true
|
||||||
|
});
|
||||||
|
|
||||||
|
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||||
|
document.getElementById("details-pane-toggle"));
|
||||||
|
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||||
|
document.querySelectorAll("#details-pane tab")[3]);
|
||||||
|
|
||||||
|
testResponseTab();
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
|
||||||
|
function testResponseTab() {
|
||||||
|
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||||
|
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||||
|
|
||||||
|
is(tab.getAttribute("selected"), "true",
|
||||||
|
"The response tab in the network details pane should be selected.");
|
||||||
|
|
||||||
|
is(tabpanel.querySelector("#response-content-info-header")
|
||||||
|
.hasAttribute("hidden"), true,
|
||||||
|
"The response info header doesn't have the intended visibility.");
|
||||||
|
is(tabpanel.querySelector("#response-content-json-box")
|
||||||
|
.hasAttribute("hidden"), false,
|
||||||
|
"The response content json box doesn't have the intended visibility.");
|
||||||
|
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||||
|
.hasAttribute("hidden"), true,
|
||||||
|
"The response content textarea box doesn't have the intended visibility.");
|
||||||
|
is(tabpanel.querySelector("#response-content-image-box")
|
||||||
|
.hasAttribute("hidden"), true,
|
||||||
|
"The response content image box doesn't have the intended visibility.");
|
||||||
|
|
||||||
|
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||||
|
"There should be 1 json scope displayed in this tabpanel.");
|
||||||
|
is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
|
||||||
|
"There should be 2 json properties displayed in this tabpanel.");
|
||||||
|
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||||
|
"The empty notice should not be displayed in this tabpanel.");
|
||||||
|
|
||||||
|
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||||
|
is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
|
||||||
|
"greeting", "The first json property name was incorrect.");
|
||||||
|
is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
|
||||||
|
"\"Hello oddly-named JSON!\"", "The first json property value was incorrect.");
|
||||||
|
|
||||||
|
is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
|
||||||
|
"__proto__", "The second json property name was incorrect.");
|
||||||
|
is(jsonScope.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
|
||||||
|
"Object", "The second json property value was incorrect.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
aDebuggee.performRequests();
|
||||||
|
});
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
|
||||||
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
|
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
|
||||||
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
|
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
|
||||||
const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
|
const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
|
||||||
|
const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
|
||||||
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
|
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
|
||||||
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
|
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
|
||||||
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
|
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Network Monitor test page</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p>JSONP test</p>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function get(aAddress, aCallback) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("GET", aAddress, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (this.readyState == this.DONE) {
|
||||||
|
aCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function performRequests() {
|
||||||
|
get("sjs_content-type-test-server.sjs?fmt=json-custom-mime", function() {
|
||||||
|
// Done.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -85,6 +85,13 @@ function handleRequest(request, response) {
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "json-custom-mime": {
|
||||||
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
|
response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
|
||||||
|
response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
|
||||||
|
response.finish();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "font": {
|
case "font": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
response.setHeader("Content-Type", "font/woff", false);
|
response.setHeader("Content-Type", "font/woff", false);
|
||||||
|
|
|
@ -62,6 +62,7 @@ EventEmitter.prototype = {
|
||||||
this.off(aEvent, handler);
|
this.off(aEvent, handler);
|
||||||
aListener.apply(null, arguments);
|
aListener.apply(null, arguments);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
handler._originalListener = aListener;
|
||||||
this.on(aEvent, handler);
|
this.on(aEvent, handler);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -79,7 +80,9 @@ EventEmitter.prototype = {
|
||||||
return;
|
return;
|
||||||
let listeners = this._eventEmitterListeners.get(aEvent);
|
let listeners = this._eventEmitterListeners.get(aEvent);
|
||||||
if (listeners) {
|
if (listeners) {
|
||||||
this._eventEmitterListeners.set(aEvent, listeners.filter(function(l) aListener != l));
|
this._eventEmitterListeners.set(aEvent, listeners.filter(l => {
|
||||||
|
return l !== aListener && l._originalListener !== aListener;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,23 @@ function testEmitter(aObject) {
|
||||||
|
|
||||||
emitter.emit("tick");
|
emitter.emit("tick");
|
||||||
|
|
||||||
|
offAfterOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
function offAfterOnce() {
|
||||||
|
let enteredC1 = false;
|
||||||
|
|
||||||
|
function c1() {
|
||||||
|
enteredC1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.once("oao", c1);
|
||||||
|
emitter.off("oao", c1);
|
||||||
|
|
||||||
|
emitter.emit("oao");
|
||||||
|
|
||||||
|
ok(!enteredC1, "c1 should not be called");
|
||||||
|
|
||||||
delete emitter;
|
delete emitter;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,11 +139,15 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
|
||||||
this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
|
this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
|
||||||
|
|
||||||
// Create bound methods.
|
// Create bound methods.
|
||||||
this.siFocusWindow = this.focusWindow.bind(this);
|
this.focusWindow = this.focusWindow.bind(this);
|
||||||
this.siBoundCopy = this.computedViewCopy.bind(this);
|
this._onContextMenu = this._onContextMenu.bind(this);
|
||||||
|
this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
|
||||||
|
this._onSelectAll = this._onSelectAll.bind(this);
|
||||||
|
this._onCopy = this._onCopy.bind(this);
|
||||||
|
|
||||||
this.styleDocument.addEventListener("copy", this.siBoundCopy);
|
this.styleDocument.addEventListener("copy", this._onCopy);
|
||||||
this.styleDocument.addEventListener("mousedown", this.siFocusWindow);
|
this.styleDocument.addEventListener("mousedown", this.focusWindow);
|
||||||
|
this.styleDocument.addEventListener("contextmenu", this._onContextMenu);
|
||||||
|
|
||||||
// Nodes used in templating
|
// Nodes used in templating
|
||||||
this.root = this.styleDocument.getElementById("root");
|
this.root = this.styleDocument.getElementById("root");
|
||||||
|
@ -161,6 +165,8 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
|
||||||
|
|
||||||
// The element that we're inspecting, and the document that it comes from.
|
// The element that we're inspecting, and the document that it comes from.
|
||||||
this.viewedElement = null;
|
this.viewedElement = null;
|
||||||
|
|
||||||
|
this._buildContextMenu();
|
||||||
this.createStyleViews();
|
this.createStyleViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,32 +472,128 @@ CssHtmlTree.prototype = {
|
||||||
*
|
*
|
||||||
* @param aEvent The event object
|
* @param aEvent The event object
|
||||||
*/
|
*/
|
||||||
focusWindow: function si_focusWindow(aEvent)
|
focusWindow: function(aEvent)
|
||||||
{
|
{
|
||||||
let win = this.styleDocument.defaultView;
|
let win = this.styleDocument.defaultView;
|
||||||
win.focus();
|
win.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy selected text.
|
* Create a context menu.
|
||||||
*
|
|
||||||
* @param aEvent The event object
|
|
||||||
*/
|
*/
|
||||||
computedViewCopy: function si_computedViewCopy(aEvent)
|
_buildContextMenu: function()
|
||||||
|
{
|
||||||
|
let doc = this.styleDocument.defaultView.parent.document;
|
||||||
|
|
||||||
|
this._contextmenu = this.styleDocument.createElementNS(XUL_NS, "menupopup");
|
||||||
|
this._contextmenu.addEventListener("popupshowing", this._contextMenuUpdate);
|
||||||
|
this._contextmenu.id = "computed-view-context-menu";
|
||||||
|
|
||||||
|
// Select All
|
||||||
|
this.menuitemSelectAll = createMenuItem(this._contextmenu, {
|
||||||
|
label: "computedView.contextmenu.selectAll",
|
||||||
|
accesskey: "computedView.contextmenu.selectAll.accessKey",
|
||||||
|
command: this._onSelectAll
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
this.menuitemCopy = createMenuItem(this._contextmenu, {
|
||||||
|
label: "computedView.contextmenu.copy",
|
||||||
|
accesskey: "computedView.contextmenu.copy.accessKey",
|
||||||
|
command: this._onCopy
|
||||||
|
});
|
||||||
|
|
||||||
|
let popupset = doc.documentElement.querySelector("popupset");
|
||||||
|
if (!popupset) {
|
||||||
|
popupset = doc.createElementNS(XUL_NS, "popupset");
|
||||||
|
doc.documentElement.appendChild(popupset);
|
||||||
|
}
|
||||||
|
popupset.appendChild(this._contextmenu);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the context menu. This means enabling or disabling menuitems as
|
||||||
|
* appropriate.
|
||||||
|
*/
|
||||||
|
_contextMenuUpdate: function()
|
||||||
{
|
{
|
||||||
let win = this.styleDocument.defaultView;
|
let win = this.styleDocument.defaultView;
|
||||||
let text = win.getSelection().toString();
|
let disable = win.getSelection().isCollapsed;
|
||||||
|
this.menuitemCopy.disabled = disable;
|
||||||
|
},
|
||||||
|
|
||||||
// Tidy up block headings by moving CSS property names and their values onto
|
/**
|
||||||
// the same line and inserting a colon between them.
|
* Context menu handler.
|
||||||
text = text.replace(/(.+)\r\n(.+)/g, "$1: $2;");
|
*/
|
||||||
text = text.replace(/(.+)\n(.+)/g, "$1: $2;");
|
_onContextMenu: function(event) {
|
||||||
|
try {
|
||||||
|
this.styleDocument.defaultView.focus();
|
||||||
|
|
||||||
let outerDoc = this.styleInspector.outerIFrame.ownerDocument;
|
this._contextmenu.openPopup(
|
||||||
clipboardHelper.copyString(text, outerDoc);
|
event.target.ownerDocument.documentElement,
|
||||||
|
"overlap", event.clientX, event.clientY, true, false, null);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
if (aEvent) {
|
/**
|
||||||
aEvent.preventDefault();
|
* Select all text.
|
||||||
|
*/
|
||||||
|
_onSelectAll: function()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let win = this.styleDocument.defaultView;
|
||||||
|
let selection = win.getSelection();
|
||||||
|
|
||||||
|
selection.selectAllChildren(this.styleDocument.documentElement);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy selected text.
|
||||||
|
*
|
||||||
|
* @param event The event object
|
||||||
|
*/
|
||||||
|
_onCopy: function(event)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let win = this.styleDocument.defaultView;
|
||||||
|
let text = win.getSelection().toString().trim();
|
||||||
|
|
||||||
|
// Tidy up block headings by moving CSS property names and their values onto
|
||||||
|
// the same line and inserting a colon between them.
|
||||||
|
let textArray = text.split(/[\r\n]+/);
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
// Parse text array to output string.
|
||||||
|
if (textArray.length > 1) {
|
||||||
|
for (let prop of textArray) {
|
||||||
|
if (CssHtmlTree.propertyNames.indexOf(prop) !== -1) {
|
||||||
|
// Property name
|
||||||
|
result += prop;
|
||||||
|
} else {
|
||||||
|
// Property value
|
||||||
|
result += ": " + prop;
|
||||||
|
if (result.length > 0) {
|
||||||
|
result += ";\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Short text fragment.
|
||||||
|
result = textArray[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboardHelper.copyString(result, this.styleDocument);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -517,32 +619,25 @@ CssHtmlTree.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove context menu
|
// Remove context menu
|
||||||
let outerDoc = this.styleInspector.outerIFrame.ownerDocument;
|
if (this._contextmenu) {
|
||||||
let menu = outerDoc.querySelector("#computed-view-context-menu");
|
// Destroy the Select All menuitem.
|
||||||
if (menu) {
|
this.menuitemCopy.removeEventListener("command", this._onCopy);
|
||||||
// Copy selected
|
this.menuitemCopy = null;
|
||||||
let menuitem = outerDoc.querySelector("#computed-view-copy");
|
|
||||||
menuitem.removeEventListener("command", this.siBoundCopy);
|
|
||||||
|
|
||||||
// Copy property
|
// Destroy the Copy menuitem.
|
||||||
menuitem = outerDoc.querySelector("#computed-view-copy-declaration");
|
this.menuitemSelectAll.removeEventListener("command", this._onSelectAll);
|
||||||
menuitem.removeEventListener("command", this.siBoundCopyDeclaration);
|
this.menuitemSelectAll = null;
|
||||||
|
|
||||||
// Copy property name
|
// Destroy the context menu.
|
||||||
menuitem = outerDoc.querySelector("#computed-view-copy-property");
|
this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate);
|
||||||
menuitem.removeEventListener("command", this.siBoundCopyProperty);
|
this._contextmenu.parentNode.removeChild(this._contextmenu);
|
||||||
|
this._contextmenu = null;
|
||||||
// Copy property value
|
|
||||||
menuitem = outerDoc.querySelector("#computed-view-copy-property-value");
|
|
||||||
menuitem.removeEventListener("command", this.siBoundCopyPropertyValue);
|
|
||||||
|
|
||||||
menu.removeEventListener("popupshowing", this.siBoundMenuUpdate);
|
|
||||||
menu.parentNode.removeChild(menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove bound listeners
|
// Remove bound listeners
|
||||||
this.styleDocument.removeEventListener("copy", this.siBoundCopy);
|
this.styleDocument.removeEventListener("contextmenu", this._onContextMenu);
|
||||||
this.styleDocument.removeEventListener("mousedown", this.siFocusWindow);
|
this.styleDocument.removeEventListener("copy", this._onCopy);
|
||||||
|
this.styleDocument.removeEventListener("mousedown", this.focusWindow);
|
||||||
|
|
||||||
// Nodes used in templating
|
// Nodes used in templating
|
||||||
delete this.root;
|
delete this.root;
|
||||||
|
@ -573,6 +668,19 @@ PropertyInfo.prototype = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createMenuItem(aMenu, aAttributes)
|
||||||
|
{
|
||||||
|
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||||
|
|
||||||
|
item.setAttribute("label", CssHtmlTree.l10n(aAttributes.label));
|
||||||
|
item.setAttribute("accesskey", CssHtmlTree.l10n(aAttributes.accesskey));
|
||||||
|
item.addEventListener("command", aAttributes.command);
|
||||||
|
|
||||||
|
aMenu.appendChild(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A container to give easy access to property data from the template engine.
|
* A container to give easy access to property data from the template engine.
|
||||||
*
|
*
|
||||||
|
@ -700,29 +808,31 @@ PropertyView.prototype = {
|
||||||
buildMain: function PropertyView_buildMain()
|
buildMain: function PropertyView_buildMain()
|
||||||
{
|
{
|
||||||
let doc = this.tree.styleDocument;
|
let doc = this.tree.styleDocument;
|
||||||
let onToggle = this.onStyleToggle.bind(this);
|
|
||||||
|
|
||||||
// Build the container element
|
// Build the container element
|
||||||
this.element = doc.createElementNS(HTML_NS, "div");
|
this.element = doc.createElementNS(HTML_NS, "div");
|
||||||
this.element.setAttribute("class", this.propertyHeaderClassName);
|
this.element.setAttribute("class", this.propertyHeaderClassName);
|
||||||
|
this.element.addEventListener("dblclick",
|
||||||
|
this.onMatchedToggle.bind(this), false);
|
||||||
|
|
||||||
// Make it keyboard navigable
|
// Make it keyboard navigable
|
||||||
this.element.setAttribute("tabindex", "0");
|
this.element.setAttribute("tabindex", "0");
|
||||||
this.element.addEventListener("keydown", function(aEvent) {
|
this.element.addEventListener("keydown", (aEvent) => {
|
||||||
let keyEvent = Ci.nsIDOMKeyEvent;
|
let keyEvent = Ci.nsIDOMKeyEvent;
|
||||||
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
|
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
|
||||||
this.mdnLinkClick();
|
this.mdnLinkClick();
|
||||||
}
|
}
|
||||||
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN ||
|
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN ||
|
||||||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
|
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
|
||||||
onToggle(aEvent);
|
this.onMatchedToggle(aEvent);
|
||||||
}
|
}
|
||||||
}.bind(this), false);
|
}, false);
|
||||||
|
|
||||||
// Build the twisty expand/collapse
|
// Build the twisty expand/collapse
|
||||||
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
|
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
|
||||||
this.matchedExpander.className = "expander theme-twisty";
|
this.matchedExpander.className = "expander theme-twisty";
|
||||||
this.matchedExpander.addEventListener("click", onToggle, false);
|
this.matchedExpander.addEventListener("click",
|
||||||
|
this.onMatchedToggle.bind(this), false);
|
||||||
this.element.appendChild(this.matchedExpander);
|
this.element.appendChild(this.matchedExpander);
|
||||||
|
|
||||||
// Build the style name element
|
// Build the style name element
|
||||||
|
@ -818,6 +928,7 @@ PropertyView.prototype = {
|
||||||
} else {
|
} else {
|
||||||
this.matchedSelectorsContainer.innerHTML = "";
|
this.matchedSelectorsContainer.innerHTML = "";
|
||||||
this.matchedExpander.removeAttribute("open");
|
this.matchedExpander.removeAttribute("open");
|
||||||
|
this.tree.styleInspector.inspector.emit("computed-view-property-collapsed");
|
||||||
return promise.resolve(undefined);
|
return promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -850,7 +961,7 @@ PropertyView.prototype = {
|
||||||
* @param {Event} aEvent Used to determine the class name of the targets click
|
* @param {Event} aEvent Used to determine the class name of the targets click
|
||||||
* event.
|
* event.
|
||||||
*/
|
*/
|
||||||
onStyleToggle: function PropertyView_onStyleToggle(aEvent)
|
onMatchedToggle: function PropertyView_onMatchedToggle(aEvent)
|
||||||
{
|
{
|
||||||
this.matchedExpanded = !this.matchedExpanded;
|
this.matchedExpanded = !this.matchedExpanded;
|
||||||
this.refreshMatchedSelectors();
|
this.refreshMatchedSelectors();
|
||||||
|
|
|
@ -1047,8 +1047,12 @@ function CssRuleView(aDoc, aStore, aPageStyle)
|
||||||
this.element.className = "ruleview devtools-monospace";
|
this.element.className = "ruleview devtools-monospace";
|
||||||
this.element.flex = 1;
|
this.element.flex = 1;
|
||||||
|
|
||||||
this._boundCopy = this._onCopy.bind(this);
|
this._buildContextMenu = this._buildContextMenu.bind(this);
|
||||||
this.element.addEventListener("copy", this._boundCopy);
|
this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
|
||||||
|
this._onSelectAll = this._onSelectAll.bind(this);
|
||||||
|
this._onCopy = this._onCopy.bind(this);
|
||||||
|
|
||||||
|
this.element.addEventListener("copy", this._onCopy);
|
||||||
|
|
||||||
this._handlePrefChange = this._handlePrefChange.bind(this);
|
this._handlePrefChange = this._handlePrefChange.bind(this);
|
||||||
gDevTools.on("pref-changed", this._handlePrefChange);
|
gDevTools.on("pref-changed", this._handlePrefChange);
|
||||||
|
@ -1060,6 +1064,7 @@ function CssRuleView(aDoc, aStore, aPageStyle)
|
||||||
};
|
};
|
||||||
this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
|
this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
|
||||||
|
|
||||||
|
this._buildContextMenu();
|
||||||
this._showEmpty();
|
this._showEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1069,6 +1074,118 @@ CssRuleView.prototype = {
|
||||||
// The element that we're inspecting.
|
// The element that we're inspecting.
|
||||||
_viewedElement: null,
|
_viewedElement: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the context menu.
|
||||||
|
*/
|
||||||
|
_buildContextMenu: function() {
|
||||||
|
let doc = this.doc.defaultView.parent.document;
|
||||||
|
|
||||||
|
this._contextmenu = doc.createElementNS(XUL_NS, "menupopup");
|
||||||
|
this._contextmenu.addEventListener("popupshowing", this._contextMenuUpdate);
|
||||||
|
this._contextmenu.id = "rule-view-context-menu";
|
||||||
|
|
||||||
|
this.menuitemSelectAll = createMenuItem(this._contextmenu, {
|
||||||
|
label: "ruleView.contextmenu.selectAll",
|
||||||
|
accesskey: "ruleView.contextmenu.selectAll.accessKey",
|
||||||
|
command: this._onSelectAll
|
||||||
|
});
|
||||||
|
this.menuitemCopy = createMenuItem(this._contextmenu, {
|
||||||
|
label: "ruleView.contextmenu.copy",
|
||||||
|
accesskey: "ruleView.contextmenu.copy.accessKey",
|
||||||
|
command: this._onCopy
|
||||||
|
});
|
||||||
|
|
||||||
|
let popupset = doc.documentElement.querySelector("popupset");
|
||||||
|
if (!popupset) {
|
||||||
|
popupset = doc.createElementNS(XUL_NS, "popupset");
|
||||||
|
doc.documentElement.appendChild(popupset);
|
||||||
|
}
|
||||||
|
|
||||||
|
popupset.appendChild(this._contextmenu);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the context menu. This means enabling or disabling menuitems as
|
||||||
|
* appropriate.
|
||||||
|
*/
|
||||||
|
_contextMenuUpdate: function() {
|
||||||
|
let win = this.doc.defaultView;
|
||||||
|
|
||||||
|
// Copy selection.
|
||||||
|
let selection = win.getSelection();
|
||||||
|
let copy;
|
||||||
|
|
||||||
|
if (selection.toString()) {
|
||||||
|
// Panel text selected
|
||||||
|
copy = true;
|
||||||
|
} else if (selection.anchorNode) {
|
||||||
|
// input type="text"
|
||||||
|
let { selectionStart, selectionEnd } = this.doc.popupNode;
|
||||||
|
|
||||||
|
if (isFinite(selectionStart) && isFinite(selectionEnd) &&
|
||||||
|
selectionStart !== selectionEnd) {
|
||||||
|
copy = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No text selected, disable copy.
|
||||||
|
copy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menuitemCopy.disabled = !copy;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select all text.
|
||||||
|
*/
|
||||||
|
_onSelectAll: function()
|
||||||
|
{
|
||||||
|
let win = this.doc.defaultView;
|
||||||
|
let selection = win.getSelection();
|
||||||
|
|
||||||
|
selection.selectAllChildren(this.doc.documentElement);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy selected text from the rule view.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* The event object.
|
||||||
|
*/
|
||||||
|
_onCopy: function(event)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let target = event.target;
|
||||||
|
let text;
|
||||||
|
|
||||||
|
if (event.target.nodeName === "menuitem") {
|
||||||
|
target = this.doc.popupNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.nodeName == "input") {
|
||||||
|
let start = Math.min(target.selectionStart, target.selectionEnd);
|
||||||
|
let end = Math.max(target.selectionStart, target.selectionEnd);
|
||||||
|
let count = end - start;
|
||||||
|
text = target.value.substr(start, count);
|
||||||
|
} else {
|
||||||
|
let win = this.doc.defaultView;
|
||||||
|
text = win.getSelection().toString();
|
||||||
|
|
||||||
|
// Remove any double newlines.
|
||||||
|
text = text.replace(/(\r?\n)\r?\n/g, "$1");
|
||||||
|
|
||||||
|
// Remove "inline"
|
||||||
|
let inline = _strings.GetStringFromName("rule.sourceInline");
|
||||||
|
let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
|
||||||
|
text = text.replace(rx, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboardHelper.copyString(text, this.doc);
|
||||||
|
event.preventDefault();
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setPageStyle: function(aPageStyle) {
|
setPageStyle: function(aPageStyle) {
|
||||||
this.pageStyle = aPageStyle;
|
this.pageStyle = aPageStyle;
|
||||||
},
|
},
|
||||||
|
@ -1094,8 +1211,27 @@ CssRuleView.prototype = {
|
||||||
|
|
||||||
gDevTools.off("pref-changed", this._handlePrefChange);
|
gDevTools.off("pref-changed", this._handlePrefChange);
|
||||||
|
|
||||||
this.element.removeEventListener("copy", this._boundCopy);
|
this.element.removeEventListener("copy", this._onCopy);
|
||||||
delete this._boundCopy;
|
delete this._onCopy;
|
||||||
|
|
||||||
|
// Remove context menu
|
||||||
|
if (this._contextmenu) {
|
||||||
|
// Destroy the Select All menuitem.
|
||||||
|
this.menuitemSelectAll.removeEventListener("command", this._onSelectAll);
|
||||||
|
this.menuitemSelectAll = null;
|
||||||
|
|
||||||
|
// Destroy the Copy menuitem.
|
||||||
|
this.menuitemCopy.removeEventListener("command", this._onCopy);
|
||||||
|
this.menuitemCopy = null;
|
||||||
|
|
||||||
|
// Destroy the context menu.
|
||||||
|
this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate);
|
||||||
|
this._contextmenu.parentNode.removeChild(this._contextmenu);
|
||||||
|
this._contextmenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We manage the popupNode ourselves so we also need to destroy it.
|
||||||
|
this.doc.popupNode = null;
|
||||||
|
|
||||||
if (this.element.parentNode) {
|
if (this.element.parentNode) {
|
||||||
this.element.parentNode.removeChild(this.element);
|
this.element.parentNode.removeChild(this.element);
|
||||||
|
@ -1342,41 +1478,6 @@ CssRuleView.prototype = {
|
||||||
this.togglePseudoElementVisibility(this.showPseudoElements);
|
this.togglePseudoElementVisibility(this.showPseudoElements);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy selected text from the rule view.
|
|
||||||
*
|
|
||||||
* @param {Event} aEvent
|
|
||||||
* The event object.
|
|
||||||
*/
|
|
||||||
_onCopy: function CssRuleView_onCopy(aEvent)
|
|
||||||
{
|
|
||||||
let target = aEvent.target;
|
|
||||||
|
|
||||||
let text;
|
|
||||||
|
|
||||||
if (target.nodeName == "input") {
|
|
||||||
let start = Math.min(target.selectionStart, target.selectionEnd);
|
|
||||||
let end = Math.max(target.selectionStart, target.selectionEnd);
|
|
||||||
let count = end - start;
|
|
||||||
text = target.value.substr(start, count);
|
|
||||||
} else {
|
|
||||||
let win = this.doc.defaultView;
|
|
||||||
text = win.getSelection().toString();
|
|
||||||
|
|
||||||
// Remove any double newlines.
|
|
||||||
text = text.replace(/(\r?\n)\r?\n/g, "$1");
|
|
||||||
|
|
||||||
// Remove "inline"
|
|
||||||
let inline = _strings.GetStringFromName("rule.sourceInline");
|
|
||||||
let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
|
|
||||||
text = text.replace(rx, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
clipboardHelper.copyString(text, this.doc);
|
|
||||||
|
|
||||||
aEvent.preventDefault();
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1458,6 +1559,22 @@ RuleEditor.prototype = {
|
||||||
this.doc.defaultView.focus();
|
this.doc.defaultView.focus();
|
||||||
}.bind(this), false);
|
}.bind(this), false);
|
||||||
|
|
||||||
|
this.element.addEventListener("contextmenu", event => {
|
||||||
|
try {
|
||||||
|
// In the sidebar we do not have this.doc.popupNode so we need to save
|
||||||
|
// the node ourselves.
|
||||||
|
this.doc.popupNode = event.explicitOriginalTarget;
|
||||||
|
let win = this.doc.defaultView;
|
||||||
|
win.focus();
|
||||||
|
|
||||||
|
this.ruleView._contextmenu.openPopup(
|
||||||
|
event.target.ownerDocument.documentElement,
|
||||||
|
"overlap", event.clientX, event.clientY, true, false, null);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
this.propertyList = createChild(code, "ul", {
|
this.propertyList = createChild(code, "ul", {
|
||||||
class: "ruleview-propertylist"
|
class: "ruleview-propertylist"
|
||||||
});
|
});
|
||||||
|
@ -2196,6 +2313,7 @@ function createChild(aParent, aTag, aAttributes)
|
||||||
function createMenuItem(aMenu, aAttributes)
|
function createMenuItem(aMenu, aAttributes)
|
||||||
{
|
{
|
||||||
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||||
|
|
||||||
item.setAttribute("label", _strings.GetStringFromName(aAttributes.label));
|
item.setAttribute("label", _strings.GetStringFromName(aAttributes.label));
|
||||||
item.setAttribute("accesskey", _strings.GetStringFromName(aAttributes.accesskey));
|
item.setAttribute("accesskey", _strings.GetStringFromName(aAttributes.accesskey));
|
||||||
item.addEventListener("command", aAttributes.command);
|
item.addEventListener("command", aAttributes.command);
|
||||||
|
|
|
@ -37,6 +37,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||||
browser_ruleview_bug_902966_revert_value_on_ESC.js \
|
browser_ruleview_bug_902966_revert_value_on_ESC.js \
|
||||||
browser_ruleview_pseudoelement.js \
|
browser_ruleview_pseudoelement.js \
|
||||||
browser_computedview_bug835808_keyboard_nav.js \
|
browser_computedview_bug835808_keyboard_nav.js \
|
||||||
|
browser_bug913014_matched_expand.js \
|
||||||
head.js \
|
head.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
let doc;
|
||||||
|
let inspector;
|
||||||
|
let view;
|
||||||
|
let viewDoc;
|
||||||
|
|
||||||
|
const DOCUMENT_URL = "data:text/html," + encodeURIComponent([
|
||||||
|
'<html>' +
|
||||||
|
'<head>' +
|
||||||
|
' <title>Computed view toggling test</title>',
|
||||||
|
' <style type="text/css"> ',
|
||||||
|
' html { color: #000000; font-size: 15pt; } ',
|
||||||
|
' h1 { color: red; } ',
|
||||||
|
' </style>',
|
||||||
|
'</head>',
|
||||||
|
'<body>',
|
||||||
|
' <h1>Some header text</h1>',
|
||||||
|
'</body>',
|
||||||
|
'</html>'
|
||||||
|
].join("\n"));
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
|
||||||
|
true);
|
||||||
|
doc = content.document;
|
||||||
|
waitForFocus(function () { openComputedView(startTests); }, content);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
content.location = DOCUMENT_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTests(aInspector, aview)
|
||||||
|
{
|
||||||
|
inspector = aInspector;
|
||||||
|
view = aview;
|
||||||
|
viewDoc = view.styleDocument;
|
||||||
|
|
||||||
|
testExpandOnTwistyClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function endTests()
|
||||||
|
{
|
||||||
|
doc = inspector = view = viewDoc = null;
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExpandOnTwistyClick()
|
||||||
|
{
|
||||||
|
let h1 = doc.querySelector("h1");
|
||||||
|
ok(h1, "H1 exists");
|
||||||
|
|
||||||
|
inspector.selection.setNode(h1);
|
||||||
|
inspector.once("inspector-updated", () => {
|
||||||
|
// Get the first twisty
|
||||||
|
let twisty = viewDoc.querySelector(".expandable");
|
||||||
|
ok(twisty, "Twisty found");
|
||||||
|
|
||||||
|
// Click and check whether it's been expanded
|
||||||
|
inspector.once("computed-view-property-expanded", () => {
|
||||||
|
// Expanded means the matchedselectors div is not empty
|
||||||
|
let div = viewDoc.querySelector(".property-content .matchedselectors");
|
||||||
|
ok(div.childNodes.length > 0, "Matched selectors are expanded on twisty click");
|
||||||
|
|
||||||
|
testCollapseOnTwistyClick();
|
||||||
|
});
|
||||||
|
twisty.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCollapseOnTwistyClick() {
|
||||||
|
// Get the same first twisty again
|
||||||
|
let twisty = viewDoc.querySelector(".expandable");
|
||||||
|
ok(twisty, "Twisty found");
|
||||||
|
|
||||||
|
// Click and check whether matched selectors are collapsed now
|
||||||
|
inspector.once("computed-view-property-collapsed", () => {
|
||||||
|
// Collapsed means the matchedselectors div is empty
|
||||||
|
let div = viewDoc.querySelector(".property-content .matchedselectors");
|
||||||
|
ok(div.childNodes.length === 0, "Matched selectors are collapsed on twisty click");
|
||||||
|
|
||||||
|
testExpandOnDblClick();
|
||||||
|
});
|
||||||
|
twisty.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExpandOnDblClick()
|
||||||
|
{
|
||||||
|
// Get the computed rule container, not the twisty this time
|
||||||
|
let container = viewDoc.querySelector(".property-view");
|
||||||
|
|
||||||
|
// Dblclick on it and check if it expands the matched selectors
|
||||||
|
inspector.once("computed-view-property-expanded", () => {
|
||||||
|
// Expanded means the matchedselectors div is not empty
|
||||||
|
let div = viewDoc.querySelector(".property-content .matchedselectors");
|
||||||
|
ok(div.childNodes.length > 0, "Matched selectors are expanded on dblclick");
|
||||||
|
|
||||||
|
testCollapseOnDblClick();
|
||||||
|
});
|
||||||
|
EventUtils.synthesizeMouseAtCenter(container, {clickCount: 2}, view.styleWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCollapseOnDblClick()
|
||||||
|
{
|
||||||
|
// Get the computed rule container, not the twisty this time
|
||||||
|
let container = viewDoc.querySelector(".property-view");
|
||||||
|
|
||||||
|
// Dblclick on it and check if it expands the matched selectors
|
||||||
|
inspector.once("computed-view-property-collapsed", () => {
|
||||||
|
// Collapsed means the matchedselectors div is empty
|
||||||
|
let div = viewDoc.querySelector(".property-content .matchedselectors");
|
||||||
|
ok(div.childNodes.length === 0, "Matched selectors are collapsed on dblclick");
|
||||||
|
|
||||||
|
endTests();
|
||||||
|
});
|
||||||
|
EventUtils.synthesizeMouseAtCenter(container, {clickCount: 2}, view.styleWindow);
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ function createDocument()
|
||||||
'</div>';
|
'</div>';
|
||||||
doc.title = "Computed view context menu test";
|
doc.title = "Computed view context menu test";
|
||||||
|
|
||||||
openComputedView(selectNode)
|
openComputedView(selectNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNode(aInspector, aComputedView)
|
function selectNode(aInspector, aComputedView)
|
||||||
|
@ -64,24 +64,51 @@ function checkCopySelection()
|
||||||
ok(props, "captain, we have the property-view nodes");
|
ok(props, "captain, we have the property-view nodes");
|
||||||
|
|
||||||
let range = document.createRange();
|
let range = document.createRange();
|
||||||
range.setStart(props[0], 0);
|
range.setStart(props[1], 0);
|
||||||
range.setEnd(props[3], 3);
|
range.setEnd(props[3], 3);
|
||||||
win.getSelection().addRange(range);
|
win.getSelection().addRange(range);
|
||||||
|
|
||||||
info("Checking that cssHtmlTree.siBoundCopy() " +
|
info("Checking that cssHtmlTree.siBoundCopy() " +
|
||||||
" returns the correct clipboard value");
|
" returns the correct clipboard value");
|
||||||
|
|
||||||
let expectedPattern = "color: #FF0;[\\r\\n]+" +
|
let expectedPattern = "font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||||
"font-family: helvetica,sans-serif;[\\r\\n]+" +
|
"font-size: 16px;[\\r\\n]+" +
|
||||||
"font-size: 16px;[\\r\\n]+" +
|
"font-variant: small-caps;[\\r\\n]*";
|
||||||
"font-variant: small-caps;[\\r\\n]*";
|
|
||||||
|
|
||||||
SimpleTest.waitForClipboard(function CS_boundCopyCheck() {
|
SimpleTest.waitForClipboard(function CS_boundCopyCheck() {
|
||||||
return checkClipboardData(expectedPattern);
|
return checkClipboardData(expectedPattern);
|
||||||
},
|
},
|
||||||
function() {fireCopyEvent(props[0])}, closeStyleInspector, function() {
|
function() {
|
||||||
failedClipboard(expectedPattern, closeStyleInspector);
|
fireCopyEvent(props[0]);
|
||||||
});
|
},
|
||||||
|
checkSelectAll,
|
||||||
|
function() {
|
||||||
|
failedClipboard(expectedPattern, checkSelectAll);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSelectAll()
|
||||||
|
{
|
||||||
|
let contentDoc = computedView.styleDocument;
|
||||||
|
let prop = contentDoc.querySelector(".property-view");
|
||||||
|
|
||||||
|
info("Checking that _SelectAll() then copy returns the correct clipboard value");
|
||||||
|
computedView._onSelectAll();
|
||||||
|
let expectedPattern = "color: #FF0;[\\r\\n]+" +
|
||||||
|
"font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||||
|
"font-size: 16px;[\\r\\n]+" +
|
||||||
|
"font-variant: small-caps;[\\r\\n]*";
|
||||||
|
|
||||||
|
SimpleTest.waitForClipboard(function() {
|
||||||
|
return checkClipboardData(expectedPattern);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
fireCopyEvent(prop);
|
||||||
|
},
|
||||||
|
finishUp,
|
||||||
|
function() {
|
||||||
|
failedClipboard(expectedPattern, finishUp);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkClipboardData(aExpectedPattern)
|
function checkClipboardData(aExpectedPattern)
|
||||||
|
@ -113,11 +140,6 @@ function failedClipboard(aExpectedPattern, aCallback)
|
||||||
aCallback();
|
aCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeStyleInspector()
|
|
||||||
{
|
|
||||||
finishUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishUp()
|
function finishUp()
|
||||||
{
|
{
|
||||||
computedView = doc = win = null;
|
computedView = doc = win = null;
|
||||||
|
|
|
@ -56,18 +56,17 @@ function highlightNode()
|
||||||
function checkCopySelection()
|
function checkCopySelection()
|
||||||
{
|
{
|
||||||
let contentDoc = win.document;
|
let contentDoc = win.document;
|
||||||
let props = contentDoc.querySelectorAll(".ruleview-property");
|
let prop = contentDoc.querySelector(".ruleview-property");
|
||||||
let values = contentDoc.querySelectorAll(".ruleview-propertycontainer");
|
let values = contentDoc.querySelectorAll(".ruleview-propertycontainer");
|
||||||
|
|
||||||
let range = document.createRange();
|
let range = contentDoc.createRange();
|
||||||
range.setStart(props[0], 0);
|
range.setStart(prop, 0);
|
||||||
range.setEnd(values[4], 2);
|
range.setEnd(values[4], 2);
|
||||||
|
|
||||||
let selection = win.getSelection();
|
let selection = win.getSelection();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
|
|
||||||
info("Checking that _boundCopy() returns the correct " +
|
info("Checking that _Copy() returns the correct clipboard value");
|
||||||
"clipboard value");
|
|
||||||
let expectedPattern = " margin: 10em;[\\r\\n]+" +
|
let expectedPattern = " margin: 10em;[\\r\\n]+" +
|
||||||
" font-size: 14pt;[\\r\\n]+" +
|
" font-size: 14pt;[\\r\\n]+" +
|
||||||
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||||
|
@ -76,20 +75,47 @@ function checkCopySelection()
|
||||||
"html {[\\r\\n]+" +
|
"html {[\\r\\n]+" +
|
||||||
" color: #000;[\\r\\n]*";
|
" color: #000;[\\r\\n]*";
|
||||||
|
|
||||||
SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
|
SimpleTest.waitForClipboard(function() {
|
||||||
return checkClipboardData(expectedPattern);
|
return checkClipboardData(expectedPattern);
|
||||||
},function() {fireCopyEvent(props[0])}, finishup, function() {
|
},
|
||||||
failedClipboard(expectedPattern, finishup);
|
function() {
|
||||||
|
fireCopyEvent(prop);
|
||||||
|
},
|
||||||
|
checkSelectAll,
|
||||||
|
function() {
|
||||||
|
failedClipboard(expectedPattern, checkSelectAll);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNode(aNode) {
|
function checkSelectAll()
|
||||||
let doc = aNode.ownerDocument;
|
{
|
||||||
let win = doc.defaultView;
|
let contentDoc = win.document;
|
||||||
let range = doc.createRange();
|
let _ruleView = ruleView();
|
||||||
|
let prop = contentDoc.querySelector(".ruleview-property");
|
||||||
|
|
||||||
range.selectNode(aNode);
|
info("Checking that _SelectAll() then copy returns the correct clipboard value");
|
||||||
win.getSelection().addRange(range);
|
_ruleView._onSelectAll();
|
||||||
|
let expectedPattern = "[\\r\\n]+" +
|
||||||
|
"element {[\\r\\n]+" +
|
||||||
|
" margin: 10em;[\\r\\n]+" +
|
||||||
|
" font-size: 14pt;[\\r\\n]+" +
|
||||||
|
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||||
|
" color: #AAA;[\\r\\n]+" +
|
||||||
|
"}[\\r\\n]+" +
|
||||||
|
"html {[\\r\\n]+" +
|
||||||
|
" color: #000;[\\r\\n]+" +
|
||||||
|
"}[\\r\\n]*";
|
||||||
|
|
||||||
|
SimpleTest.waitForClipboard(function() {
|
||||||
|
return checkClipboardData(expectedPattern);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
fireCopyEvent(prop);
|
||||||
|
},
|
||||||
|
finishup,
|
||||||
|
function() {
|
||||||
|
failedClipboard(expectedPattern, finishup);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkClipboardData(aExpectedPattern)
|
function checkClipboardData(aExpectedPattern)
|
||||||
|
|
|
@ -64,8 +64,8 @@ function openComputedView(callback)
|
||||||
openInspector(inspector => {
|
openInspector(inspector => {
|
||||||
inspector.sidebar.once("computedview-ready", () => {
|
inspector.sidebar.once("computedview-ready", () => {
|
||||||
inspector.sidebar.select("computedview");
|
inspector.sidebar.select("computedview");
|
||||||
let ruleView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
|
let computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
|
||||||
callback(inspector, ruleView);
|
callback(inspector, computedView);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -413,6 +413,7 @@ Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.proto
|
||||||
render().element.appendChild(urlnode);
|
render().element.appendChild(urlnode);
|
||||||
this.element.classList.add("navigation-marker");
|
this.element.classList.add("navigation-marker");
|
||||||
this.element.url = this._url;
|
this.element.url = this._url;
|
||||||
|
this.element.appendChild(doc.createTextNode("\n"));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
|
@ -136,6 +136,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||||
browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js \
|
browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js \
|
||||||
browser_webconsole_allow_mixedcontent_securityerrors.js \
|
browser_webconsole_allow_mixedcontent_securityerrors.js \
|
||||||
browser_webconsole_block_mixedcontent_securityerrors.js \
|
browser_webconsole_block_mixedcontent_securityerrors.js \
|
||||||
|
browser_webconsole_output_copy_newlines.js \
|
||||||
head.js \
|
head.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,9 @@ function consoleOpened(aHud) {
|
||||||
isnot(selection.indexOf("bug587617"), -1,
|
isnot(selection.indexOf("bug587617"), -1,
|
||||||
"selection text includes 'bug587617'");
|
"selection text includes 'bug587617'");
|
||||||
|
|
||||||
waitForClipboard(selection, () => goDoCommand("cmd_copy"),
|
waitForClipboard((str) => { return selection.trim() == str.trim(); },
|
||||||
testContextMenuCopy, testContextMenuCopy);
|
() => { goDoCommand("cmd_copy") },
|
||||||
|
testContextMenuCopy, testContextMenuCopy);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +75,9 @@ function testContextMenuCopy() {
|
||||||
|
|
||||||
copyItem.doCommand();
|
copyItem.doCommand();
|
||||||
|
|
||||||
waitForClipboard(selection, () => goDoCommand("cmd_copy"),
|
waitForClipboard((str) => { return selection.trim() == str.trim(); },
|
||||||
finishTest, finishTest);
|
() => { goDoCommand("cmd_copy") },
|
||||||
|
finishTest, finishTest);
|
||||||
HUD = outputNode = null;
|
HUD = outputNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ let openTabs = [];
|
||||||
let loadedTabCount = 0;
|
let loadedTabCount = 0;
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
requestLongerTimeout(2);
|
requestLongerTimeout(3);
|
||||||
|
|
||||||
// Add two tabs in the main window.
|
// Add two tabs in the main window.
|
||||||
addTabs(win1);
|
addTabs(win1);
|
||||||
|
@ -45,6 +45,7 @@ function addTabs(aWindow) {
|
||||||
tab.linkedBrowser.removeEventListener(aEvent.type, onLoad, true);
|
tab.linkedBrowser.removeEventListener(aEvent.type, onLoad, true);
|
||||||
|
|
||||||
loadedTabCount++;
|
loadedTabCount++;
|
||||||
|
info("tabs loaded: " + loadedTabCount);
|
||||||
if (loadedTabCount >= 4) {
|
if (loadedTabCount >= 4) {
|
||||||
executeSoon(openConsoles);
|
executeSoon(openConsoles);
|
||||||
}
|
}
|
||||||
|
@ -62,19 +63,12 @@ function openConsoles() {
|
||||||
let window = hud.target.tab.linkedBrowser.contentWindow;
|
let window = hud.target.tab.linkedBrowser.contentWindow;
|
||||||
window.console.log("message for tab " + index);
|
window.console.log("message for tab " + index);
|
||||||
consolesOpen++;
|
consolesOpen++;
|
||||||
|
if (consolesOpen == 4) {
|
||||||
|
// Use executeSoon() to allow the promise to resolve.
|
||||||
|
executeSoon(closeConsoles);
|
||||||
|
}
|
||||||
}.bind(null, i));
|
}.bind(null, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForSuccess({
|
|
||||||
timeout: 15000,
|
|
||||||
name: "4 web consoles opened",
|
|
||||||
validatorFn: function()
|
|
||||||
{
|
|
||||||
return consolesOpen == 4;
|
|
||||||
},
|
|
||||||
successFn: closeConsoles,
|
|
||||||
failureFn: closeConsoles,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeConsoles() {
|
function closeConsoles() {
|
||||||
|
@ -83,32 +77,25 @@ function closeConsoles() {
|
||||||
function onWebConsoleClose(aSubject, aTopic) {
|
function onWebConsoleClose(aSubject, aTopic) {
|
||||||
if (aTopic == "web-console-destroyed") {
|
if (aTopic == "web-console-destroyed") {
|
||||||
consolesClosed++;
|
consolesClosed++;
|
||||||
|
info("consoles destroyed: " + consolesClosed);
|
||||||
|
if (consolesClosed == 4) {
|
||||||
|
// Use executeSoon() to allow all the observers to execute.
|
||||||
|
executeSoon(finishTest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
|
Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
|
||||||
|
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
Services.obs.removeObserver(onWebConsoleClose, "web-console-destroyed");
|
||||||
|
});
|
||||||
|
|
||||||
win2.close();
|
win2.close();
|
||||||
|
|
||||||
win1.gBrowser.removeTab(openTabs[0]);
|
win1.gBrowser.removeTab(openTabs[0]);
|
||||||
win1.gBrowser.removeTab(openTabs[1]);
|
win1.gBrowser.removeTab(openTabs[1]);
|
||||||
|
|
||||||
openTabs = win1 = win2 = null;
|
openTabs = win1 = win2 = null;
|
||||||
|
|
||||||
function onTimeout() {
|
|
||||||
Services.obs.removeObserver(onWebConsoleClose, "web-console-destroyed");
|
|
||||||
executeSoon(finishTest);
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForSuccess({
|
|
||||||
timeout: 10000,
|
|
||||||
name: "4 web consoles closed",
|
|
||||||
validatorFn: function()
|
|
||||||
{
|
|
||||||
return consolesClosed == 4;
|
|
||||||
},
|
|
||||||
successFn: onTimeout,
|
|
||||||
failureFn: onTimeout,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,6 @@ function performTest(HUD, [result]) {
|
||||||
isnot(selectionText.indexOf("foobarBazBug613280"), -1,
|
isnot(selectionText.indexOf("foobarBazBug613280"), -1,
|
||||||
"selection text includes 'foobarBazBug613280'");
|
"selection text includes 'foobarBazBug613280'");
|
||||||
|
|
||||||
waitForClipboard(selectionText, clipboard_setup, clipboard_copy_done,
|
waitForClipboard((str) => { return str.trim() == selectionText.trim(); },
|
||||||
clipboard_copy_done);
|
clipboard_setup, clipboard_copy_done, clipboard_copy_done);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// Test that multiple messages are copied into the clipboard and that they are
|
||||||
|
// separated by new lines. See bug 916997.
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
const TEST_URI = "data:text/html;charset=utf8,<p>hello world, bug 916997";
|
||||||
|
let clipboardValue = "";
|
||||||
|
|
||||||
|
addTab(TEST_URI);
|
||||||
|
browser.addEventListener("load", function onLoad() {
|
||||||
|
browser.removeEventListener("load", onLoad, true);
|
||||||
|
openConsole(null, consoleOpened);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
function consoleOpened(hud)
|
||||||
|
{
|
||||||
|
hud.jsterm.clearOutput();
|
||||||
|
|
||||||
|
let controller = top.document.commandDispatcher.
|
||||||
|
getControllerForCommand("cmd_copy");
|
||||||
|
is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
|
||||||
|
|
||||||
|
content.console.log("Hello world! bug916997a");
|
||||||
|
content.console.log("Hello world 2! bug916997b");
|
||||||
|
|
||||||
|
waitForMessages({
|
||||||
|
webconsole: hud,
|
||||||
|
messages: [{
|
||||||
|
text: "Hello world! bug916997a",
|
||||||
|
category: CATEGORY_WEBDEV,
|
||||||
|
severity: SEVERITY_LOG,
|
||||||
|
}, {
|
||||||
|
text: "Hello world 2! bug916997b",
|
||||||
|
category: CATEGORY_WEBDEV,
|
||||||
|
severity: SEVERITY_LOG,
|
||||||
|
}],
|
||||||
|
}).then(() => {
|
||||||
|
hud.ui.output.selectAllMessages();
|
||||||
|
hud.outputNode.focus();
|
||||||
|
|
||||||
|
goUpdateCommand("cmd_copy");
|
||||||
|
controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
|
||||||
|
is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
|
||||||
|
|
||||||
|
let selection = hud.iframeWindow.getSelection() + "";
|
||||||
|
info("selection '" + selection + "'");
|
||||||
|
waitForClipboard((str) => {
|
||||||
|
clipboardValue = str;
|
||||||
|
return str.indexOf("bug916997a") > -1 && str.indexOf("bug916997b") > -1;
|
||||||
|
},
|
||||||
|
() => { goDoCommand("cmd_copy"); },
|
||||||
|
checkClipboard.bind(null, hud),
|
||||||
|
() => {
|
||||||
|
info("last clipboard value: '" + clipboardValue + "'");
|
||||||
|
finishTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkClipboard(hud)
|
||||||
|
{
|
||||||
|
info("clipboard value '" + clipboardValue + "'");
|
||||||
|
let lines = clipboardValue.trim().split("\n");
|
||||||
|
is(hud.outputNode.children.length, 2, "number of messages");
|
||||||
|
is(lines.length, hud.outputNode.children.length, "number of lines");
|
||||||
|
isnot(lines[0].indexOf("bug916997a"), -1,
|
||||||
|
"first message text includes 'bug916997a'");
|
||||||
|
isnot(lines[1].indexOf("bug916997b"), -1,
|
||||||
|
"second message text includes 'bug916997b'");
|
||||||
|
is(lines[0].indexOf("bug916997b"), -1,
|
||||||
|
"first message text does not include 'bug916997b'");
|
||||||
|
finishTest();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2417,6 +2417,7 @@ WebConsoleFrame.prototype = {
|
||||||
if (locationNode) {
|
if (locationNode) {
|
||||||
node.appendChild(locationNode);
|
node.appendChild(locationNode);
|
||||||
}
|
}
|
||||||
|
node.appendChild(this.document.createTextNode("\n"));
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,3 +57,35 @@ rule.warning.title=Invalid property value
|
||||||
# LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
|
# LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
|
||||||
# first opened and there's no node selected in the rule view.
|
# first opened and there's no node selected in the rule view.
|
||||||
rule.empty=No element selected.
|
rule.empty=No element selected.
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (ruleView.contextmenu.selectAll): Text displayed in the
|
||||||
|
# rule view context menu.
|
||||||
|
ruleView.contextmenu.selectAll=Select all
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (ruleView.contextmenu.selectAll.accessKey): Access key for
|
||||||
|
# the rule view context menu "Select all" entry.
|
||||||
|
ruleView.contextmenu.selectAll.accessKey=A
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (ruleView.contextmenu.copy): Text displayed in the rule view
|
||||||
|
# context menu.
|
||||||
|
ruleView.contextmenu.copy=Copy
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (ruleView.contextmenu.copy.accessKey): Access key for
|
||||||
|
# the rule view context menu "Select all" entry.
|
||||||
|
ruleView.contextmenu.copy.accessKey=C
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (computedView.contextmenu.selectAll): Text displayed in the
|
||||||
|
# computed view context menu.
|
||||||
|
computedView.contextmenu.selectAll=Select all
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (computedView.contextmenu.selectAll.accessKey): Access key for
|
||||||
|
# the computed view context menu "Select all" entry.
|
||||||
|
computedView.contextmenu.selectAll.accessKey=A
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (computedView.contextmenu.copy): Text displayed in the
|
||||||
|
# computed view context menu.
|
||||||
|
computedView.contextmenu.copy=Copy
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (computedView.contextmenu.copy.accessKey): Access key for
|
||||||
|
# the computed view context menu "Select all" entry.
|
||||||
|
computedView.contextmenu.copy.accessKey=C
|
||||||
|
|
|
@ -2159,7 +2159,7 @@ sidebarheader > .tabs-closebutton > .toolbarbutton-text {
|
||||||
|
|
||||||
.browserContainer > findbar {
|
.browserContainer > findbar {
|
||||||
background: @scopeBarBackground@;
|
background: @scopeBarBackground@;
|
||||||
border-bottom: @scopeBarSeparatorBorder@;
|
border-top: @scopeBarSeparatorBorder@;
|
||||||
color: -moz-DialogText;
|
color: -moz-DialogText;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1395,7 +1395,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||||
animator.setUseHardwareLayer(false);
|
animator.setUseHardwareLayer(false);
|
||||||
|
|
||||||
mBrowserToolbar.startEditing(url, animator);
|
mBrowserToolbar.startEditing(url, animator);
|
||||||
showHomePagerWithAnimator(HomePager.Page.HISTORY, animator);
|
showHomePagerWithAnimator(HomePager.Page.TOP_SITES, animator);
|
||||||
|
|
||||||
animator.start();
|
animator.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,6 @@ FENNEC_JAVA_FILES = \
|
||||||
home/BookmarksListView.java \
|
home/BookmarksListView.java \
|
||||||
home/BookmarksPage.java \
|
home/BookmarksPage.java \
|
||||||
home/BookmarkFolderView.java \
|
home/BookmarkFolderView.java \
|
||||||
home/BookmarkThumbnailView.java \
|
|
||||||
home/BrowserSearch.java \
|
home/BrowserSearch.java \
|
||||||
home/HistoryPage.java \
|
home/HistoryPage.java \
|
||||||
home/HomeFragment.java \
|
home/HomeFragment.java \
|
||||||
|
@ -228,9 +227,8 @@ FENNEC_JAVA_FILES = \
|
||||||
home/FadedTextView.java \
|
home/FadedTextView.java \
|
||||||
home/LastTabsPage.java \
|
home/LastTabsPage.java \
|
||||||
home/MostRecentPage.java \
|
home/MostRecentPage.java \
|
||||||
home/MostVisitedPage.java \
|
|
||||||
home/MultiTypeCursorAdapter.java \
|
home/MultiTypeCursorAdapter.java \
|
||||||
home/PinBookmarkDialog.java \
|
home/PinSiteDialog.java \
|
||||||
home/ReadingListPage.java \
|
home/ReadingListPage.java \
|
||||||
home/SearchEngine.java \
|
home/SearchEngine.java \
|
||||||
home/SearchEngineRow.java \
|
home/SearchEngineRow.java \
|
||||||
|
@ -238,9 +236,10 @@ FENNEC_JAVA_FILES = \
|
||||||
home/SimpleCursorLoader.java \
|
home/SimpleCursorLoader.java \
|
||||||
home/SuggestClient.java \
|
home/SuggestClient.java \
|
||||||
home/TabMenuStrip.java \
|
home/TabMenuStrip.java \
|
||||||
home/TopBookmarkItemView.java \
|
home/TopSitesGridItemView.java \
|
||||||
home/TopBookmarksAdapter.java \
|
home/TopSitesGridView.java \
|
||||||
home/TopBookmarksView.java \
|
home/TopSitesPage.java \
|
||||||
|
home/TopSitesThumbnailView.java \
|
||||||
home/TwoLinePageRow.java \
|
home/TwoLinePageRow.java \
|
||||||
menu/GeckoMenu.java \
|
menu/GeckoMenu.java \
|
||||||
menu/GeckoMenuInflater.java \
|
menu/GeckoMenuInflater.java \
|
||||||
|
@ -476,12 +475,12 @@ RES_LAYOUT = \
|
||||||
res/layout/home_last_tabs_page.xml \
|
res/layout/home_last_tabs_page.xml \
|
||||||
res/layout/home_history_list.xml \
|
res/layout/home_history_list.xml \
|
||||||
res/layout/home_most_recent_page.xml \
|
res/layout/home_most_recent_page.xml \
|
||||||
res/layout/home_most_visited_page.xml \
|
|
||||||
res/layout/home_pager.xml \
|
res/layout/home_pager.xml \
|
||||||
res/layout/home_reading_list_page.xml \
|
res/layout/home_reading_list_page.xml \
|
||||||
res/layout/home_search_item_row.xml \
|
res/layout/home_search_item_row.xml \
|
||||||
res/layout/home_banner.xml \
|
res/layout/home_banner.xml \
|
||||||
res/layout/home_suggestion_prompt.xml \
|
res/layout/home_suggestion_prompt.xml \
|
||||||
|
res/layout/home_top_sites_page.xml \
|
||||||
res/layout/web_app.xml \
|
res/layout/web_app.xml \
|
||||||
res/layout/launch_app_list.xml \
|
res/layout/launch_app_list.xml \
|
||||||
res/layout/launch_app_listitem.xml \
|
res/layout/launch_app_listitem.xml \
|
||||||
|
@ -491,7 +490,7 @@ RES_LAYOUT = \
|
||||||
res/layout/notification_icon_text.xml \
|
res/layout/notification_icon_text.xml \
|
||||||
res/layout/notification_progress.xml \
|
res/layout/notification_progress.xml \
|
||||||
res/layout/notification_progress_text.xml \
|
res/layout/notification_progress_text.xml \
|
||||||
res/layout/pin_bookmark_dialog.xml \
|
res/layout/pin_site_dialog.xml \
|
||||||
res/layout/preference_rightalign_icon.xml \
|
res/layout/preference_rightalign_icon.xml \
|
||||||
res/layout/preference_search_engine.xml \
|
res/layout/preference_search_engine.xml \
|
||||||
res/layout/preference_search_tip.xml \
|
res/layout/preference_search_tip.xml \
|
||||||
|
@ -510,7 +509,7 @@ RES_LAYOUT = \
|
||||||
res/layout/tabs_item_cell.xml \
|
res/layout/tabs_item_cell.xml \
|
||||||
res/layout/tabs_item_row.xml \
|
res/layout/tabs_item_row.xml \
|
||||||
res/layout/text_selection_handles.xml \
|
res/layout/text_selection_handles.xml \
|
||||||
res/layout/top_bookmark_item_view.xml \
|
res/layout/top_sites_grid_item_view.xml \
|
||||||
res/layout/two_line_page_row.xml \
|
res/layout/two_line_page_row.xml \
|
||||||
res/layout/list_item_header.xml \
|
res/layout/list_item_header.xml \
|
||||||
res/layout/select_dialog_list.xml \
|
res/layout/select_dialog_list.xml \
|
||||||
|
@ -709,12 +708,13 @@ RES_DRAWABLE_MDPI = \
|
||||||
res/drawable-mdpi/menu_item_check.png \
|
res/drawable-mdpi/menu_item_check.png \
|
||||||
res/drawable-mdpi/menu_item_more.png \
|
res/drawable-mdpi/menu_item_more.png \
|
||||||
res/drawable-mdpi/menu_item_uncheck.png \
|
res/drawable-mdpi/menu_item_uncheck.png \
|
||||||
|
res/drawable-mdpi/pin.png \
|
||||||
res/drawable-mdpi/shield.png \
|
res/drawable-mdpi/shield.png \
|
||||||
res/drawable-mdpi/shield_doorhanger.png \
|
res/drawable-mdpi/shield_doorhanger.png \
|
||||||
res/drawable-mdpi/tabs_normal.png \
|
res/drawable-mdpi/tabs_normal.png \
|
||||||
res/drawable-mdpi/tabs_private.png \
|
res/drawable-mdpi/tabs_private.png \
|
||||||
res/drawable-mdpi/tabs_synced.png \
|
res/drawable-mdpi/tabs_synced.png \
|
||||||
res/drawable-mdpi/top_bookmark_add.png \
|
res/drawable-mdpi/top_site_add.png \
|
||||||
res/drawable-mdpi/urlbar_stop.png \
|
res/drawable-mdpi/urlbar_stop.png \
|
||||||
res/drawable-mdpi/reader.png \
|
res/drawable-mdpi/reader.png \
|
||||||
res/drawable-mdpi/reader_cropped.png \
|
res/drawable-mdpi/reader_cropped.png \
|
||||||
|
@ -817,12 +817,13 @@ RES_DRAWABLE_HDPI = \
|
||||||
res/drawable-hdpi/menu_item_check.png \
|
res/drawable-hdpi/menu_item_check.png \
|
||||||
res/drawable-hdpi/menu_item_more.png \
|
res/drawable-hdpi/menu_item_more.png \
|
||||||
res/drawable-hdpi/menu_item_uncheck.png \
|
res/drawable-hdpi/menu_item_uncheck.png \
|
||||||
|
res/drawable-hdpi/pin.png \
|
||||||
res/drawable-hdpi/shield.png \
|
res/drawable-hdpi/shield.png \
|
||||||
res/drawable-hdpi/shield_doorhanger.png \
|
res/drawable-hdpi/shield_doorhanger.png \
|
||||||
res/drawable-hdpi/tabs_normal.png \
|
res/drawable-hdpi/tabs_normal.png \
|
||||||
res/drawable-hdpi/tabs_private.png \
|
res/drawable-hdpi/tabs_private.png \
|
||||||
res/drawable-hdpi/tabs_synced.png \
|
res/drawable-hdpi/tabs_synced.png \
|
||||||
res/drawable-hdpi/top_bookmark_add.png \
|
res/drawable-hdpi/top_site_add.png \
|
||||||
res/drawable-hdpi/urlbar_stop.png \
|
res/drawable-hdpi/urlbar_stop.png \
|
||||||
res/drawable-hdpi/reader.png \
|
res/drawable-hdpi/reader.png \
|
||||||
res/drawable-hdpi/reader_cropped.png \
|
res/drawable-hdpi/reader_cropped.png \
|
||||||
|
@ -897,7 +898,7 @@ RES_DRAWABLE_XHDPI = \
|
||||||
res/drawable-xhdpi/find_close.png \
|
res/drawable-xhdpi/find_close.png \
|
||||||
res/drawable-xhdpi/find_next.png \
|
res/drawable-xhdpi/find_next.png \
|
||||||
res/drawable-xhdpi/find_prev.png \
|
res/drawable-xhdpi/find_prev.png \
|
||||||
res/drawable-xhdpi/top_bookmark_add.png \
|
res/drawable-xhdpi/top_site_add.png \
|
||||||
res/drawable-xhdpi/urlbar_stop.png \
|
res/drawable-xhdpi/urlbar_stop.png \
|
||||||
res/drawable-xhdpi/reader.png \
|
res/drawable-xhdpi/reader.png \
|
||||||
res/drawable-xhdpi/reader_cropped.png \
|
res/drawable-xhdpi/reader_cropped.png \
|
||||||
|
@ -915,6 +916,7 @@ RES_DRAWABLE_XHDPI = \
|
||||||
res/drawable-xhdpi/menu_item_check.png \
|
res/drawable-xhdpi/menu_item_check.png \
|
||||||
res/drawable-xhdpi/menu_item_more.png \
|
res/drawable-xhdpi/menu_item_more.png \
|
||||||
res/drawable-xhdpi/menu_item_uncheck.png \
|
res/drawable-xhdpi/menu_item_uncheck.png \
|
||||||
|
res/drawable-xhdpi/pin.png \
|
||||||
res/drawable-xhdpi/shield.png \
|
res/drawable-xhdpi/shield.png \
|
||||||
res/drawable-xhdpi/shield_doorhanger.png \
|
res/drawable-xhdpi/shield_doorhanger.png \
|
||||||
res/drawable-xhdpi/tab_indicator_divider.9.png \
|
res/drawable-xhdpi/tab_indicator_divider.9.png \
|
||||||
|
@ -1073,7 +1075,7 @@ RES_COLOR = \
|
||||||
res/color/select_item_multichoice.xml \
|
res/color/select_item_multichoice.xml \
|
||||||
res/color/tertiary_text.xml \
|
res/color/tertiary_text.xml \
|
||||||
res/color/tertiary_text_inverse.xml \
|
res/color/tertiary_text_inverse.xml \
|
||||||
res/color/top_bookmark_item_title.xml \
|
res/color/top_sites_grid_item_title.xml \
|
||||||
res/color/url_bar_title.xml \
|
res/color/url_bar_title.xml \
|
||||||
res/color/url_bar_title_hint.xml \
|
res/color/url_bar_title_hint.xml \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
@ -1083,7 +1085,7 @@ RES_MENU = \
|
||||||
res/menu/gecko_app_menu.xml \
|
res/menu/gecko_app_menu.xml \
|
||||||
res/menu/home_contextmenu.xml \
|
res/menu/home_contextmenu.xml \
|
||||||
res/menu/titlebar_contextmenu.xml \
|
res/menu/titlebar_contextmenu.xml \
|
||||||
res/menu/top_bookmarks_contextmenu.xml \
|
res/menu/top_sites_contextmenu.xml \
|
||||||
res/menu-large-v11/browser_app_menu.xml \
|
res/menu-large-v11/browser_app_menu.xml \
|
||||||
res/menu-v11/browser_app_menu.xml \
|
res/menu-v11/browser_app_menu.xml \
|
||||||
res/menu-xlarge-v11/browser_app_menu.xml \
|
res/menu-xlarge-v11/browser_app_menu.xml \
|
||||||
|
@ -1101,7 +1103,7 @@ RES_DRAWABLE += \
|
||||||
$(SYNC_RES_DRAWABLE) \
|
$(SYNC_RES_DRAWABLE) \
|
||||||
res/drawable/action_bar_button.xml \
|
res/drawable/action_bar_button.xml \
|
||||||
res/drawable/action_bar_button_inverse.xml \
|
res/drawable/action_bar_button_inverse.xml \
|
||||||
res/drawable/bookmark_thumbnail_bg.xml \
|
res/drawable/top_sites_thumbnail_bg.xml \
|
||||||
res/drawable/url_bar_bg.xml \
|
res/drawable/url_bar_bg.xml \
|
||||||
res/drawable/url_bar_entry.xml \
|
res/drawable/url_bar_entry.xml \
|
||||||
res/drawable/url_bar_nav_button.xml \
|
res/drawable/url_bar_nav_button.xml \
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class Tab {
|
||||||
mUserSearch = "";
|
mUserSearch = "";
|
||||||
mExternal = external;
|
mExternal = external;
|
||||||
mParentId = parentId;
|
mParentId = parentId;
|
||||||
mAboutHomePage = HomePager.Page.BOOKMARKS;
|
mAboutHomePage = HomePager.Page.TOP_SITES;
|
||||||
mTitle = title == null ? "" : title;
|
mTitle = title == null ? "" : title;
|
||||||
mFavicon = null;
|
mFavicon = null;
|
||||||
mFaviconUrl = null;
|
mFaviconUrl = null;
|
||||||
|
|
|
@ -27,6 +27,8 @@ import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
@ -71,6 +73,7 @@ public class UpdateService extends IntentService {
|
||||||
|
|
||||||
private NotificationManager mNotificationManager;
|
private NotificationManager mNotificationManager;
|
||||||
private ConnectivityManager mConnectivityManager;
|
private ConnectivityManager mConnectivityManager;
|
||||||
|
private Builder mBuilder;
|
||||||
|
|
||||||
private boolean mDownloading;
|
private boolean mDownloading;
|
||||||
private boolean mApplyImmediately;
|
private boolean mApplyImmediately;
|
||||||
|
@ -116,6 +119,8 @@ public class UpdateService extends IntentService {
|
||||||
registerForUpdates(false);
|
registerForUpdates(false);
|
||||||
} else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
|
} else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
|
||||||
startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
|
startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
|
||||||
|
// Use this instead for forcing a download from about:fennec
|
||||||
|
// startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL);
|
||||||
} else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) {
|
} else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) {
|
||||||
// We always want to do the download here
|
// We always want to do the download here
|
||||||
startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD);
|
startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD);
|
||||||
|
@ -269,7 +274,7 @@ public class UpdateService extends IntentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException {
|
private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException {
|
||||||
Log.i(LOGTAG, "openning connection with url: " + url);
|
Log.i(LOGTAG, "opening connection with url: " + url);
|
||||||
|
|
||||||
ProxySelector ps = ProxySelector.getDefault();
|
ProxySelector ps = ProxySelector.getDefault();
|
||||||
Proxy proxy = Proxy.NO_PROXY;
|
Proxy proxy = Proxy.NO_PROXY;
|
||||||
|
@ -312,7 +317,7 @@ public class UpdateService extends IntentService {
|
||||||
if (urlNode == null || hashFunctionNode == null ||
|
if (urlNode == null || hashFunctionNode == null ||
|
||||||
hashValueNode == null || sizeNode == null) {
|
hashValueNode == null || sizeNode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in UpdateInfo from the XML data
|
// Fill in UpdateInfo from the XML data
|
||||||
UpdateInfo info = new UpdateInfo();
|
UpdateInfo info = new UpdateInfo();
|
||||||
|
@ -364,7 +369,6 @@ public class UpdateService extends IntentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDownloadNotification(File downloadFile) {
|
private void showDownloadNotification(File downloadFile) {
|
||||||
Notification notification = new Notification(android.R.drawable.stat_sys_download, null, System.currentTimeMillis());
|
|
||||||
|
|
||||||
Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
|
Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
|
||||||
notificationIntent.setClass(this, UpdateService.class);
|
notificationIntent.setClass(this, UpdateService.class);
|
||||||
|
@ -374,11 +378,14 @@ public class UpdateService extends IntentService {
|
||||||
|
|
||||||
PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title),
|
mBuilder = new NotificationCompat.Builder(this);
|
||||||
mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select),
|
mBuilder.setContentTitle(getResources().getString(R.string.updater_downloading_title))
|
||||||
contentIntent);
|
.setContentText(mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select))
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
mNotificationManager.notify(NOTIFICATION_ID, notification);
|
.setContentIntent(contentIntent);
|
||||||
|
|
||||||
|
mBuilder.setProgress(100, 0, true);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDownloadFailure() {
|
private void showDownloadFailure() {
|
||||||
|
@ -433,11 +440,17 @@ public class UpdateService extends IntentService {
|
||||||
int len = 0;
|
int len = 0;
|
||||||
|
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
float lastPercent = 0.0f;
|
int lastNotify = 0;
|
||||||
|
|
||||||
while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
|
while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
|
||||||
output.write(buf, 0, len);
|
output.write(buf, 0, len);
|
||||||
bytesRead += len;
|
bytesRead += len;
|
||||||
|
// Updating the notification takes time so only do it every 1MB
|
||||||
|
if(bytesRead - lastNotify > 1048576) {
|
||||||
|
mBuilder.setProgress(length, bytesRead, false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
||||||
|
lastNotify = bytesRead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(LOGTAG, "completed update download!");
|
Log.i(LOGTAG, "completed update download!");
|
||||||
|
|
|
@ -39,9 +39,9 @@ public class BrowserDB {
|
||||||
|
|
||||||
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit);
|
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit);
|
||||||
|
|
||||||
// This should only return frecent bookmarks, BrowserDB.getTopBookmarks will do the
|
// This should onlyl return frecent sites, BrowserDB.getTopSites will do the
|
||||||
// work to combine that list with the pinned sites list
|
// work to combine that list with the pinned sites list
|
||||||
public Cursor getTopBookmarks(ContentResolver cr, int limit);
|
public Cursor getTopSites(ContentResolver cr, int limit);
|
||||||
|
|
||||||
public void updateVisitedHistory(ContentResolver cr, String uri);
|
public void updateVisitedHistory(ContentResolver cr, String uri);
|
||||||
|
|
||||||
|
@ -138,12 +138,12 @@ public class BrowserDB {
|
||||||
return sDb.filter(cr, constraint, limit);
|
return sDb.filter(cr, constraint, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cursor getTopBookmarks(ContentResolver cr, int limit) {
|
public static Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
|
||||||
// Note this is not a single query anymore, but actually returns a mixture of two queries,
|
// Note this is not a single query anymore, but actually returns a mixture
|
||||||
// one for top bookmarks, and one for pinned sites (which are actually bookmarks as well).
|
// of two queries, one for topSites and one for pinned sites.
|
||||||
Cursor topBookmarks = sDb.getTopBookmarks(cr, limit);
|
Cursor pinnedSites = sDb.getPinnedSites(cr, minLimit);
|
||||||
Cursor pinnedSites = sDb.getPinnedSites(cr, limit);
|
Cursor topSites = sDb.getTopSites(cr, maxLimit - pinnedSites.getCount());
|
||||||
return new TopSitesCursorWrapper(pinnedSites, topBookmarks, limit);
|
return new TopSitesCursorWrapper(pinnedSites, topSites, minLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateVisitedHistory(ContentResolver cr, String uri) {
|
public static void updateVisitedHistory(ContentResolver cr, String uri) {
|
||||||
|
@ -342,12 +342,12 @@ public class BrowserDB {
|
||||||
int mSize = 0;
|
int mSize = 0;
|
||||||
private SparseArray<PinnedSite> mPinnedSites = null;
|
private SparseArray<PinnedSite> mPinnedSites = null;
|
||||||
|
|
||||||
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int size) {
|
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int minSize) {
|
||||||
super(normalCursor);
|
super(normalCursor);
|
||||||
|
|
||||||
setPinnedSites(pinnedCursor);
|
setPinnedSites(pinnedCursor);
|
||||||
mCursor = normalCursor;
|
mCursor = normalCursor;
|
||||||
mSize = size;
|
mSize = Math.max(minSize, mPinnedSites.size() + mCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPinnedSites(Cursor c) {
|
public void setPinnedSites(Cursor c) {
|
||||||
|
@ -452,6 +452,20 @@ public class BrowserDB {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int columnIndex) {
|
||||||
|
if (hasPinnedSites()) {
|
||||||
|
PinnedSite site = getPinnedSite(mIndex);
|
||||||
|
if (site != null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!super.isBeforeFirst() && !super.isAfterLast())
|
||||||
|
return super.getInt(columnIndex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getString(int columnIndex) {
|
public String getString(int columnIndex) {
|
||||||
if (hasPinnedSites()) {
|
if (hasPinnedSites()) {
|
||||||
|
|
|
@ -231,13 +231,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getTopBookmarks(ContentResolver cr, int limit) {
|
public Cursor getTopSites(ContentResolver cr, int limit) {
|
||||||
// Only select bookmarks. Unfortunately, we need to query the combined view,
|
// Filter out sites that are pinned
|
||||||
// instead of just the bookmarks table, in order to do the frecency calculation.
|
String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " +
|
||||||
String selection = Combined.BOOKMARK_ID + " IS NOT NULL";
|
|
||||||
|
|
||||||
// Filter out sites that are pinned.
|
|
||||||
selection = DBUtils.concatenateWhere(selection, Combined.URL + " NOT IN (SELECT " +
|
|
||||||
Bookmarks.URL + " FROM bookmarks WHERE " +
|
Bookmarks.URL + " FROM bookmarks WHERE " +
|
||||||
DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " +
|
DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " +
|
||||||
DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)");
|
DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)");
|
||||||
|
@ -245,7 +241,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||||
return filterAllSites(cr,
|
return filterAllSites(cr,
|
||||||
new String[] { Combined._ID,
|
new String[] { Combined._ID,
|
||||||
Combined.URL,
|
Combined.URL,
|
||||||
Combined.TITLE },
|
Combined.TITLE,
|
||||||
|
Combined.DISPLAY,
|
||||||
|
Combined.BOOKMARK_ID,
|
||||||
|
Combined.HISTORY_ID },
|
||||||
"",
|
"",
|
||||||
limit,
|
limit,
|
||||||
BrowserDB.ABOUT_PAGES_URL_FILTER,
|
BrowserDB.ABOUT_PAGES_URL_FILTER,
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
@ -31,12 +30,6 @@ public class BookmarksListView extends HomeListView
|
||||||
implements AdapterView.OnItemClickListener{
|
implements AdapterView.OnItemClickListener{
|
||||||
public static final String LOGTAG = "GeckoBookmarksListView";
|
public static final String LOGTAG = "GeckoBookmarksListView";
|
||||||
|
|
||||||
// The last motion event that was intercepted.
|
|
||||||
private MotionEvent mMotionEvent;
|
|
||||||
|
|
||||||
// The default touch slop.
|
|
||||||
private int mTouchSlop;
|
|
||||||
|
|
||||||
public BookmarksListView(Context context) {
|
public BookmarksListView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
@ -47,9 +40,6 @@ public class BookmarksListView extends HomeListView
|
||||||
|
|
||||||
public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
|
public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
// Scaled touch slop for this context.
|
|
||||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,36 +60,6 @@ public class BookmarksListView extends HomeListView
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
|
||||||
switch(event.getAction() & MotionEvent.ACTION_MASK) {
|
|
||||||
case MotionEvent.ACTION_DOWN: {
|
|
||||||
// Store the event by obtaining a copy.
|
|
||||||
mMotionEvent = MotionEvent.obtain(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_MOVE: {
|
|
||||||
if ((mMotionEvent != null) &&
|
|
||||||
(Math.abs(event.getY() - mMotionEvent.getY()) > mTouchSlop)) {
|
|
||||||
// The user is scrolling. Pass the last event to this view,
|
|
||||||
// and make this view scroll.
|
|
||||||
onTouchEvent(mMotionEvent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
mMotionEvent = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do default interception.
|
|
||||||
return super.onInterceptTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
final ListView list = (ListView) parent;
|
final ListView list = (ListView) parent;
|
||||||
|
|
|
@ -8,21 +8,12 @@ package org.mozilla.gecko.home;
|
||||||
import org.mozilla.gecko.favicons.Favicons;
|
import org.mozilla.gecko.favicons.Favicons;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.Tabs;
|
import org.mozilla.gecko.Tabs;
|
||||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
|
||||||
import org.mozilla.gecko.animation.PropertyAnimator.Property;
|
|
||||||
import org.mozilla.gecko.animation.ViewHelper;
|
|
||||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||||
import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
|
import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
|
||||||
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
|
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
import org.mozilla.gecko.home.PinBookmarkDialog.OnBookmarkSelectedListener;
|
|
||||||
import org.mozilla.gecko.home.TopBookmarksAdapter.Thumbnail;
|
|
||||||
import org.mozilla.gecko.home.TopBookmarksView.OnPinBookmarkListener;
|
|
||||||
import org.mozilla.gecko.home.TopBookmarksView.TopBookmarksContextMenuInfo;
|
|
||||||
import org.mozilla.gecko.util.ThreadUtils;
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -32,27 +23,14 @@ import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnTouchListener;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A page in about:home that displays a ListView of bookmarks.
|
* A page in about:home that displays a ListView of bookmarks.
|
||||||
|
@ -63,57 +41,24 @@ public class BookmarksPage extends HomeFragment {
|
||||||
// Cursor loader ID for list of bookmarks.
|
// Cursor loader ID for list of bookmarks.
|
||||||
private static final int LOADER_ID_BOOKMARKS_LIST = 0;
|
private static final int LOADER_ID_BOOKMARKS_LIST = 0;
|
||||||
|
|
||||||
// Cursor loader ID for grid of bookmarks.
|
|
||||||
private static final int LOADER_ID_TOP_BOOKMARKS = 1;
|
|
||||||
|
|
||||||
// Loader ID for thumbnails.
|
|
||||||
private static final int LOADER_ID_THUMBNAILS = 2;
|
|
||||||
|
|
||||||
// Key for bookmarks folder id.
|
// Key for bookmarks folder id.
|
||||||
private static final String BOOKMARKS_FOLDER_KEY = "folder_id";
|
private static final String BOOKMARKS_FOLDER_KEY = "folder_id";
|
||||||
|
|
||||||
// Key for thumbnail urls.
|
|
||||||
private static final String THUMBNAILS_URLS_KEY = "urls";
|
|
||||||
|
|
||||||
// List of bookmarks.
|
// List of bookmarks.
|
||||||
private BookmarksListView mList;
|
private BookmarksListView mList;
|
||||||
|
|
||||||
// Grid of top bookmarks.
|
|
||||||
private TopBookmarksView mTopBookmarks;
|
|
||||||
|
|
||||||
// Banner to show snippets.
|
|
||||||
private HomeBanner mBanner;
|
|
||||||
|
|
||||||
// Adapter for list of bookmarks.
|
// Adapter for list of bookmarks.
|
||||||
private BookmarksListAdapter mListAdapter;
|
private BookmarksListAdapter mListAdapter;
|
||||||
|
|
||||||
// Adapter for grid of bookmarks.
|
|
||||||
private TopBookmarksAdapter mTopBookmarksAdapter;
|
|
||||||
|
|
||||||
// Callback for cursor loaders.
|
// Callback for cursor loaders.
|
||||||
private CursorLoaderCallbacks mLoaderCallbacks;
|
private CursorLoaderCallbacks mLoaderCallbacks;
|
||||||
|
|
||||||
// Callback for thumbnail loader.
|
|
||||||
private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
|
|
||||||
|
|
||||||
// Listener for pinning bookmarks.
|
|
||||||
private PinBookmarkListener mPinBookmarkListener;
|
|
||||||
|
|
||||||
// Raw Y value of the last event that happened on the list view.
|
|
||||||
private float mListTouchY = -1;
|
|
||||||
|
|
||||||
// Scrolling direction of the banner.
|
|
||||||
private boolean mSnapBannerToTop;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false);
|
final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false);
|
||||||
|
|
||||||
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
|
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
|
||||||
|
|
||||||
mTopBookmarks = new TopBookmarksView(getActivity());
|
|
||||||
mList.addHeaderView(mTopBookmarks);
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,26 +74,10 @@ public class BookmarksPage extends HomeFragment {
|
||||||
+ " must implement HomePager.OnUrlOpenListener");
|
+ " must implement HomePager.OnUrlOpenListener");
|
||||||
}
|
}
|
||||||
|
|
||||||
mPinBookmarkListener = new PinBookmarkListener();
|
|
||||||
|
|
||||||
mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
|
mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
|
||||||
mList.setOnUrlOpenListener(listener);
|
mList.setOnUrlOpenListener(listener);
|
||||||
mList.setHeaderDividersEnabled(false);
|
|
||||||
|
|
||||||
mTopBookmarks.setOnUrlOpenListener(listener);
|
|
||||||
mTopBookmarks.setOnPinBookmarkListener(mPinBookmarkListener);
|
|
||||||
|
|
||||||
registerForContextMenu(mList);
|
registerForContextMenu(mList);
|
||||||
registerForContextMenu(mTopBookmarks);
|
|
||||||
|
|
||||||
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
|
|
||||||
mList.setOnTouchListener(new OnTouchListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
BookmarksPage.this.handleListTouchEvent(event);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -157,10 +86,6 @@ public class BookmarksPage extends HomeFragment {
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
// Setup the top bookmarks adapter.
|
|
||||||
mTopBookmarksAdapter = new TopBookmarksAdapter(activity, null);
|
|
||||||
mTopBookmarks.setAdapter(mTopBookmarksAdapter);
|
|
||||||
|
|
||||||
// Setup the list adapter.
|
// Setup the list adapter.
|
||||||
mListAdapter = new BookmarksListAdapter(activity, null);
|
mListAdapter = new BookmarksListAdapter(activity, null);
|
||||||
mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() {
|
mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() {
|
||||||
|
@ -180,7 +105,6 @@ public class BookmarksPage extends HomeFragment {
|
||||||
|
|
||||||
// Create callbacks before the initial loader is started.
|
// Create callbacks before the initial loader is started.
|
||||||
mLoaderCallbacks = new CursorLoaderCallbacks();
|
mLoaderCallbacks = new CursorLoaderCallbacks();
|
||||||
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
|
|
||||||
loadIfVisible();
|
loadIfVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,9 +112,6 @@ public class BookmarksPage extends HomeFragment {
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
mList = null;
|
mList = null;
|
||||||
mListAdapter = null;
|
mListAdapter = null;
|
||||||
mTopBookmarks = null;
|
|
||||||
mTopBookmarksAdapter = null;
|
|
||||||
mPinBookmarkListener = null;
|
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,204 +135,9 @@ public class BookmarksPage extends HomeFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleListTouchEvent(MotionEvent event) {
|
|
||||||
// Ignore the event if the banner is hidden for this session.
|
|
||||||
if (mBanner.isDismissed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.getActionMasked()) {
|
|
||||||
case MotionEvent.ACTION_DOWN: {
|
|
||||||
mListTouchY = event.getRawY();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_MOVE: {
|
|
||||||
// There is a chance that we won't receive ACTION_DOWN, if the touch event
|
|
||||||
// actually started on the Grid instead of the List. Treat this as first event.
|
|
||||||
if (mListTouchY == -1) {
|
|
||||||
mListTouchY = event.getRawY();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final float curY = event.getRawY();
|
|
||||||
final float delta = mListTouchY - curY;
|
|
||||||
mSnapBannerToTop = (delta > 0.0f) ? false : true;
|
|
||||||
|
|
||||||
final float height = mBanner.getHeight();
|
|
||||||
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
|
|
||||||
|
|
||||||
// Clamp the values to be between 0 and height.
|
|
||||||
if (newTranslationY < 0.0f) {
|
|
||||||
newTranslationY = 0.0f;
|
|
||||||
} else if (newTranslationY > height) {
|
|
||||||
newTranslationY = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewHelper.setTranslationY(mBanner, newTranslationY);
|
|
||||||
mListTouchY = curY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_CANCEL: {
|
|
||||||
mListTouchY = -1;
|
|
||||||
final float y = ViewHelper.getTranslationY(mBanner);
|
|
||||||
final float height = mBanner.getHeight();
|
|
||||||
if (y > 0.0f && y < height) {
|
|
||||||
final PropertyAnimator animator = new PropertyAnimator(100);
|
|
||||||
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
|
|
||||||
animator.start();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
|
|
||||||
if (menuInfo == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HomeFragment will handle the default case.
|
|
||||||
if (menuInfo instanceof HomeContextMenuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, view, menuInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(menuInfo instanceof TopBookmarksContextMenuInfo)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuInflater inflater = new MenuInflater(view.getContext());
|
|
||||||
inflater.inflate(R.menu.top_bookmarks_contextmenu, menu);
|
|
||||||
|
|
||||||
TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo;
|
|
||||||
menu.setHeaderTitle(info.getDisplayTitle());
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.url)) {
|
|
||||||
if (info.isPinned) {
|
|
||||||
menu.findItem(R.id.top_bookmarks_pin).setVisible(false);
|
|
||||||
} else {
|
|
||||||
menu.findItem(R.id.top_bookmarks_unpin).setVisible(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
menu.findItem(R.id.top_bookmarks_open_new_tab).setVisible(false);
|
|
||||||
menu.findItem(R.id.top_bookmarks_open_private_tab).setVisible(false);
|
|
||||||
menu.findItem(R.id.top_bookmarks_pin).setVisible(false);
|
|
||||||
menu.findItem(R.id.top_bookmarks_unpin).setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
ContextMenuInfo menuInfo = item.getMenuInfo();
|
|
||||||
|
|
||||||
// HomeFragment will handle the default case.
|
|
||||||
if (menuInfo == null || !(menuInfo instanceof TopBookmarksContextMenuInfo)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo;
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
|
|
||||||
final int itemId = item.getItemId();
|
|
||||||
if (itemId == R.id.top_bookmarks_open_new_tab || itemId == R.id.top_bookmarks_open_private_tab) {
|
|
||||||
if (info.url == null) {
|
|
||||||
Log.e(LOGTAG, "Can't open in new tab because URL is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
|
|
||||||
if (item.getItemId() == R.id.top_bookmarks_open_private_tab)
|
|
||||||
flags |= Tabs.LOADURL_PRIVATE;
|
|
||||||
|
|
||||||
Tabs.getInstance().loadUrl(info.url, flags);
|
|
||||||
Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemId == R.id.top_bookmarks_pin) {
|
|
||||||
final String url = info.url;
|
|
||||||
final String title = info.title;
|
|
||||||
final int position = info.position;
|
|
||||||
final Context context = getActivity().getApplicationContext();
|
|
||||||
|
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemId == R.id.top_bookmarks_unpin) {
|
|
||||||
final int position = info.position;
|
|
||||||
final Context context = getActivity().getApplicationContext();
|
|
||||||
|
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BrowserDB.unpinSite(context.getContentResolver(), position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemId == R.id.top_bookmarks_edit) {
|
|
||||||
mPinBookmarkListener.onPinBookmark(info.position);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load() {
|
protected void load() {
|
||||||
final LoaderManager manager = getLoaderManager();
|
getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
|
||||||
manager.initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
|
|
||||||
manager.initLoader(LOADER_ID_TOP_BOOKMARKS, null, mLoaderCallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for pinning bookmarks.
|
|
||||||
*/
|
|
||||||
private class PinBookmarkListener implements OnPinBookmarkListener,
|
|
||||||
OnBookmarkSelectedListener {
|
|
||||||
// Tag for the PinBookmarkDialog fragment.
|
|
||||||
private static final String TAG_PIN_BOOKMARK = "pin_bookmark";
|
|
||||||
|
|
||||||
// Position of the pin.
|
|
||||||
private int mPosition;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPinBookmark(int position) {
|
|
||||||
mPosition = position;
|
|
||||||
|
|
||||||
final FragmentManager manager = getActivity().getSupportFragmentManager();
|
|
||||||
PinBookmarkDialog dialog = (PinBookmarkDialog) manager.findFragmentByTag(TAG_PIN_BOOKMARK);
|
|
||||||
if (dialog == null) {
|
|
||||||
dialog = PinBookmarkDialog.newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.setOnBookmarkSelectedListener(this);
|
|
||||||
dialog.show(manager, TAG_PIN_BOOKMARK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBookmarkSelected(final String url, final String title) {
|
|
||||||
final int position = mPosition;
|
|
||||||
final Context context = getActivity().getApplicationContext();
|
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -435,221 +161,28 @@ public class BookmarksPage extends HomeFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader for the grid for top bookmarks.
|
|
||||||
*/
|
|
||||||
private static class TopBookmarksLoader extends SimpleCursorLoader {
|
|
||||||
public TopBookmarksLoader(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor loadCursor() {
|
|
||||||
final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites);
|
|
||||||
return BrowserDB.getTopBookmarks(getContext().getContentResolver(), max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader callbacks for the LoaderManager of this fragment.
|
* Loader callbacks for the LoaderManager of this fragment.
|
||||||
*/
|
*/
|
||||||
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
switch(id) {
|
if (args == null) {
|
||||||
case LOADER_ID_BOOKMARKS_LIST: {
|
return new BookmarksLoader(getActivity());
|
||||||
if (args == null) {
|
} else {
|
||||||
return new BookmarksLoader(getActivity());
|
return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
|
||||||
} else {
|
|
||||||
return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOADER_ID_TOP_BOOKMARKS: {
|
|
||||||
return new TopBookmarksLoader(getActivity());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||||
final int loaderId = loader.getId();
|
mListAdapter.swapCursor(c);
|
||||||
switch(loaderId) {
|
|
||||||
case LOADER_ID_BOOKMARKS_LIST: {
|
|
||||||
mListAdapter.swapCursor(c);
|
|
||||||
mList.setHeaderDividersEnabled(c != null && c.getCount() > 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOADER_ID_TOP_BOOKMARKS: {
|
|
||||||
mTopBookmarksAdapter.swapCursor(c);
|
|
||||||
|
|
||||||
// Load the thumbnails.
|
|
||||||
if (c.getCount() > 0 && c.moveToFirst()) {
|
|
||||||
final ArrayList<String> urls = new ArrayList<String>();
|
|
||||||
do {
|
|
||||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
|
||||||
urls.add(url);
|
|
||||||
} while (c.moveToNext());
|
|
||||||
|
|
||||||
if (urls.size() > 0) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
|
|
||||||
getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
final int loaderId = loader.getId();
|
if (mList != null) {
|
||||||
switch(loaderId) {
|
mListAdapter.swapCursor(null);
|
||||||
case LOADER_ID_BOOKMARKS_LIST: {
|
|
||||||
if (mList != null) {
|
|
||||||
mListAdapter.swapCursor(null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOADER_ID_TOP_BOOKMARKS: {
|
|
||||||
if (mTopBookmarks != null) {
|
|
||||||
mTopBookmarksAdapter.swapCursor(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An AsyncTaskLoader to load the thumbnails from a cursor.
|
|
||||||
*/
|
|
||||||
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
|
|
||||||
private Map<String, Thumbnail> mThumbnails;
|
|
||||||
private ArrayList<String> mUrls;
|
|
||||||
|
|
||||||
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
|
|
||||||
super(context);
|
|
||||||
mUrls = urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Thumbnail> loadInBackground() {
|
|
||||||
if (mUrls == null || mUrls.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
|
|
||||||
|
|
||||||
// Query the DB for thumbnails.
|
|
||||||
final ContentResolver cr = getContext().getContentResolver();
|
|
||||||
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
do {
|
|
||||||
// Try to get the thumbnail, if cursor is valid.
|
|
||||||
String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
|
|
||||||
final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
|
|
||||||
final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
|
|
||||||
|
|
||||||
if (bitmap != null) {
|
|
||||||
thumbnails.put(url, new Thumbnail(bitmap, true));
|
|
||||||
}
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the DB for favicons for the urls without thumbnails.
|
|
||||||
for (String url : mUrls) {
|
|
||||||
if (!thumbnails.containsKey(url)) {
|
|
||||||
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
|
|
||||||
if (bitmap != null) {
|
|
||||||
// Favicons.scaleImage can return several different size favicons,
|
|
||||||
// but will at least prevent this from being too large.
|
|
||||||
thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return thumbnails;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deliverResult(Map<String, Thumbnail> thumbnails) {
|
|
||||||
if (isReset()) {
|
|
||||||
mThumbnails = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mThumbnails = thumbnails;
|
|
||||||
|
|
||||||
if (isStarted()) {
|
|
||||||
super.deliverResult(thumbnails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
if (mThumbnails != null) {
|
|
||||||
deliverResult(mThumbnails);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (takeContentChanged() || mThumbnails == null) {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopLoading() {
|
|
||||||
cancelLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCanceled(Map<String, Thumbnail> thumbnails) {
|
|
||||||
mThumbnails = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onReset() {
|
|
||||||
super.onReset();
|
|
||||||
|
|
||||||
// Ensure the loader is stopped.
|
|
||||||
onStopLoading();
|
|
||||||
|
|
||||||
mThumbnails = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader callbacks for the thumbnails on TopBookmarksView.
|
|
||||||
*/
|
|
||||||
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
|
|
||||||
@Override
|
|
||||||
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
|
|
||||||
if (mTopBookmarksAdapter != null) {
|
|
||||||
mTopBookmarksAdapter.updateThumbnails(thumbnails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
|
|
||||||
if (mTopBookmarksAdapter != null) {
|
|
||||||
mTopBookmarksAdapter.updateThumbnails(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.LinearGradient;
|
import android.graphics.LinearGradient;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
@ -24,6 +25,9 @@ public class FadedTextView extends TextView {
|
||||||
// Width of the fade effect from end of the view.
|
// Width of the fade effect from end of the view.
|
||||||
private int mFadeWidth;
|
private int mFadeWidth;
|
||||||
|
|
||||||
|
// Padding for compound drawables.
|
||||||
|
private int mCompoundPadding;
|
||||||
|
|
||||||
public FadedTextView(Context context) {
|
public FadedTextView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +42,8 @@ public class FadedTextView extends TextView {
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView);
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView);
|
||||||
mFadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0);
|
mFadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
|
mCompoundPadding = getCompoundDrawablePadding();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,11 +56,18 @@ public class FadedTextView extends TextView {
|
||||||
// Layout doesn't return a proper width for getWidth().
|
// Layout doesn't return a proper width for getWidth().
|
||||||
// Instead check the width of the first line, as we've restricted to just one line.
|
// Instead check the width of the first line, as we've restricted to just one line.
|
||||||
if (getLayout().getLineWidth(0) > width) {
|
if (getLayout().getLineWidth(0) > width) {
|
||||||
|
final Drawable leftDrawable = getCompoundDrawables()[0];
|
||||||
|
int drawableWidth = 0;
|
||||||
|
if (leftDrawable != null) {
|
||||||
|
drawableWidth = leftDrawable.getIntrinsicWidth() + mCompoundPadding;
|
||||||
|
width -= drawableWidth;
|
||||||
|
}
|
||||||
|
|
||||||
int color = getCurrentTextColor();
|
int color = getCurrentTextColor();
|
||||||
float stop = ((float) (width - mFadeWidth) / (float) width);
|
float stop = ((float) (width - mFadeWidth) / (float) width);
|
||||||
LinearGradient gradient = new LinearGradient(0, 0, width, 0,
|
LinearGradient gradient = new LinearGradient(0, 0, width, 0,
|
||||||
new int[] { color, color, 0x0 },
|
new int[] { color, color, 0x0 },
|
||||||
new float[] { 0, stop, 1.0f },
|
new float[] { 0, stop, 1.0f - (drawableWidth / width) },
|
||||||
Shader.TileMode.CLAMP);
|
Shader.TileMode.CLAMP);
|
||||||
getPaint().setShader(gradient);
|
getPaint().setShader(gradient);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class HistoryPage extends HomeFragment
|
||||||
private static final String LOGTAG = "GeckoHistoryPage";
|
private static final String LOGTAG = "GeckoHistoryPage";
|
||||||
private IconTabWidget mTabWidget;
|
private IconTabWidget mTabWidget;
|
||||||
private int mSelectedTab;
|
private int mSelectedTab;
|
||||||
private boolean initializeVisitedPage;
|
private boolean initializeRecentPage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
@ -38,7 +38,6 @@ public class HistoryPage extends HomeFragment
|
||||||
|
|
||||||
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
|
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
|
||||||
|
|
||||||
mTabWidget.addTab(R.drawable.icon_most_visited, R.string.home_most_visited_title);
|
|
||||||
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
|
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
|
||||||
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
|
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
|
||||||
|
|
||||||
|
@ -50,11 +49,11 @@ public class HistoryPage extends HomeFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
// Show most visited page as the initial page.
|
// Show most recent page as the initial page.
|
||||||
// Since we detach/attach on config change, this prevents from replacing current fragment.
|
// Since we detach/attach on config change, this prevents from replacing current fragment.
|
||||||
if (!initializeVisitedPage) {
|
if (!initializeRecentPage) {
|
||||||
showMostVisitedPage();
|
showMostRecentPage();
|
||||||
initializeVisitedPage = true;
|
initializeRecentPage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,10 +64,8 @@ public class HistoryPage extends HomeFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
showMostVisitedPage();
|
|
||||||
} else if (index == 1) {
|
|
||||||
showMostRecentPage();
|
showMostRecentPage();
|
||||||
} else if (index == 2) {
|
} else if (index == 1) {
|
||||||
showLastTabsPage();
|
showLastTabsPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,15 +92,10 @@ public class HistoryPage extends HomeFragment
|
||||||
subPage.setArguments(args);
|
subPage.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.addToBackStack(null).replace(R.id.visited_page_container, subPage)
|
.addToBackStack(null).replace(R.id.history_page_container, subPage)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showMostVisitedPage() {
|
|
||||||
final MostVisitedPage mostVisitedPage = MostVisitedPage.newInstance();
|
|
||||||
showSubPage(mostVisitedPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMostRecentPage() {
|
private void showMostRecentPage() {
|
||||||
final MostRecentPage mostRecentPage = MostRecentPage.newInstance();
|
final MostRecentPage mostRecentPage = MostRecentPage.newInstance();
|
||||||
showSubPage(mostRecentPage);
|
showSubPage(mostRecentPage);
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.mozilla.gecko.home;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||||
import org.mozilla.gecko.animation.ViewHelper;
|
import org.mozilla.gecko.animation.ViewHelper;
|
||||||
|
import org.mozilla.gecko.util.HardwareUtils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -38,6 +39,7 @@ public class HomePager extends ViewPager {
|
||||||
// List of pages in order.
|
// List of pages in order.
|
||||||
public enum Page {
|
public enum Page {
|
||||||
HISTORY,
|
HISTORY,
|
||||||
|
TOP_SITES,
|
||||||
BOOKMARKS,
|
BOOKMARKS,
|
||||||
READING_LIST
|
READING_LIST
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,7 @@ public class HomePager extends ViewPager {
|
||||||
static final String LIST_TAG_HISTORY = "history";
|
static final String LIST_TAG_HISTORY = "history";
|
||||||
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
||||||
static final String LIST_TAG_READING_LIST = "reading_list";
|
static final String LIST_TAG_READING_LIST = "reading_list";
|
||||||
static final String LIST_TAG_MOST_VISITED = "most_visited";
|
static final String LIST_TAG_TOP_SITES = "top_sites";
|
||||||
static final String LIST_TAG_MOST_RECENT = "most_recent";
|
static final String LIST_TAG_MOST_RECENT = "most_recent";
|
||||||
static final String LIST_TAG_LAST_TABS = "last_tabs";
|
static final String LIST_TAG_LAST_TABS = "last_tabs";
|
||||||
|
|
||||||
|
@ -90,9 +92,9 @@ public class HomePager extends ViewPager {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
|
||||||
// This is to keep all 3 pages in memory after they are
|
// This is to keep all 4 pages in memory after they are
|
||||||
// selected in the pager.
|
// selected in the pager.
|
||||||
setOffscreenPageLimit(2);
|
setOffscreenPageLimit(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,14 +133,19 @@ public class HomePager extends ViewPager {
|
||||||
// Only animate on post-HC devices, when a non-null animator is given
|
// Only animate on post-HC devices, when a non-null animator is given
|
||||||
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
|
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
|
||||||
|
|
||||||
// Add the pages to the adapter in order.
|
adapter.addTab(Page.TOP_SITES, TopSitesPage.class, new Bundle(),
|
||||||
adapter.addTab(Page.HISTORY, HistoryPage.class, new Bundle(),
|
getContext().getString(R.string.home_top_sites_title));
|
||||||
getContext().getString(R.string.home_history_title));
|
|
||||||
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
|
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
|
||||||
getContext().getString(R.string.bookmarks_title));
|
getContext().getString(R.string.bookmarks_title));
|
||||||
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
|
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
|
||||||
getContext().getString(R.string.reading_list_title));
|
getContext().getString(R.string.reading_list_title));
|
||||||
|
|
||||||
|
// On phones, the history tab is the first tab. On tablets, the
|
||||||
|
// history tab is the last tab.
|
||||||
|
adapter.addTab(HardwareUtils.isTablet() ? -1 : 0,
|
||||||
|
Page.HISTORY, HistoryPage.class, new Bundle(),
|
||||||
|
getContext().getString(R.string.home_history_title));
|
||||||
|
|
||||||
adapter.setCanLoadHint(!shouldAnimate);
|
adapter.setCanLoadHint(!shouldAnimate);
|
||||||
|
|
||||||
setAdapter(adapter);
|
setAdapter(adapter);
|
||||||
|
@ -226,8 +233,18 @@ public class HomePager extends ViewPager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
|
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
|
||||||
|
addTab(-1, page, clss, args, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTab(int index, Page page, Class<?> clss, Bundle args, String title) {
|
||||||
TabInfo info = new TabInfo(page, clss, args, title);
|
TabInfo info = new TabInfo(page, clss, args, title);
|
||||||
mTabs.add(info);
|
|
||||||
|
if (index >= 0) {
|
||||||
|
mTabs.add(index, info);
|
||||||
|
} else {
|
||||||
|
mTabs.add(info);
|
||||||
|
}
|
||||||
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
|
|
||||||
if (mDecor != null) {
|
if (mDecor != null) {
|
||||||
|
|
|
@ -1,225 +0,0 @@
|
||||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.mozilla.gecko.home;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewStub;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment that displays frecency search results in a ListView.
|
|
||||||
*/
|
|
||||||
public class MostVisitedPage extends HomeFragment {
|
|
||||||
// Logging tag name
|
|
||||||
private static final String LOGTAG = "GeckoMostVisitedPage";
|
|
||||||
|
|
||||||
// Cursor loader ID for search query
|
|
||||||
private static final int LOADER_ID_FRECENCY = 0;
|
|
||||||
|
|
||||||
// Adapter for the list of search results
|
|
||||||
private VisitedAdapter mAdapter;
|
|
||||||
|
|
||||||
// The view shown by the fragment.
|
|
||||||
private ListView mList;
|
|
||||||
|
|
||||||
// The title for this HomeFragment page.
|
|
||||||
private TextView mTitle;
|
|
||||||
|
|
||||||
// Reference to the View to display when there are no results.
|
|
||||||
private View mEmptyView;
|
|
||||||
|
|
||||||
// Callbacks used for the search and favicon cursor loaders
|
|
||||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
|
||||||
|
|
||||||
// On URL open listener
|
|
||||||
private OnUrlOpenListener mUrlOpenListener;
|
|
||||||
|
|
||||||
public static MostVisitedPage newInstance() {
|
|
||||||
return new MostVisitedPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MostVisitedPage() {
|
|
||||||
mUrlOpenListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException(activity.toString()
|
|
||||||
+ " must implement HomePager.OnUrlOpenListener");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
|
|
||||||
mUrlOpenListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
return inflater.inflate(R.layout.home_most_visited_page, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
mTitle = (TextView) view.findViewById(R.id.title);
|
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setText(R.string.home_most_visited_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
mList = (HomeListView) view.findViewById(R.id.list);
|
|
||||||
mList.setTag(HomePager.LIST_TAG_MOST_VISITED);
|
|
||||||
|
|
||||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
final Cursor c = mAdapter.getCursor();
|
|
||||||
if (c == null || !c.moveToPosition(position)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
|
||||||
|
|
||||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
|
||||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
registerForContextMenu(mList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
mList = null;
|
|
||||||
mTitle = null;
|
|
||||||
mEmptyView = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
// Intialize the search adapter
|
|
||||||
mAdapter = new VisitedAdapter(getActivity(), null);
|
|
||||||
mList.setAdapter(mAdapter);
|
|
||||||
|
|
||||||
// Create callbacks before the initial loader is started
|
|
||||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
|
||||||
loadIfVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void load() {
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_FRECENCY, null, mCursorLoaderCallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUiFromCursor(Cursor c) {
|
|
||||||
if (c != null && c.getCount() > 0) {
|
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor is empty, so hide the title and set the
|
|
||||||
// empty view if it hasn't been set already.
|
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEmptyView == null) {
|
|
||||||
// Set empty page view. We delay this so that the empty view won't flash.
|
|
||||||
ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
|
||||||
mEmptyView = emptyViewStub.inflate();
|
|
||||||
|
|
||||||
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
|
||||||
emptyIcon.setImageResource(R.drawable.icon_most_visited_empty);
|
|
||||||
|
|
||||||
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
|
||||||
emptyText.setText(R.string.home_most_visited_empty);
|
|
||||||
|
|
||||||
mList.setEmptyView(mEmptyView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FrecencyCursorLoader extends SimpleCursorLoader {
|
|
||||||
// Max number of search results
|
|
||||||
private static final int SEARCH_LIMIT = 50;
|
|
||||||
|
|
||||||
public FrecencyCursorLoader(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor loadCursor() {
|
|
||||||
final ContentResolver cr = getContext().getContentResolver();
|
|
||||||
return BrowserDB.filter(cr, "", SEARCH_LIMIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VisitedAdapter extends CursorAdapter {
|
|
||||||
public VisitedAdapter(Context context, Cursor cursor) {
|
|
||||||
super(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
|
||||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
|
||||||
row.updateFromCursor(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
|
||||||
return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, parent, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
|
||||||
@Override
|
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new FrecencyCursorLoader(getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
|
||||||
mAdapter.swapCursor(c);
|
|
||||||
updateUiFromCursor(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
|
||||||
mAdapter.swapCursor(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,12 +28,12 @@ import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog fragment that displays frecency search results, for pinning as a bookmark, in a ListView.
|
* Dialog fragment that displays frecency search results, for pinning a site, in a GridView.
|
||||||
*/
|
*/
|
||||||
class PinBookmarkDialog extends DialogFragment {
|
class PinSiteDialog extends DialogFragment {
|
||||||
// Listener for url selection
|
// Listener for url selection
|
||||||
public static interface OnBookmarkSelectedListener {
|
public static interface OnSiteSelectedListener {
|
||||||
public void onBookmarkSelected(String url, String title);
|
public void onSiteSelected(String url, String title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor loader ID for search query
|
// Cursor loader ID for search query
|
||||||
|
@ -55,13 +55,13 @@ class PinBookmarkDialog extends DialogFragment {
|
||||||
private CursorLoaderCallbacks mLoaderCallbacks;
|
private CursorLoaderCallbacks mLoaderCallbacks;
|
||||||
|
|
||||||
// Bookmark selected listener
|
// Bookmark selected listener
|
||||||
private OnBookmarkSelectedListener mOnBookmarkSelectedListener;
|
private OnSiteSelectedListener mOnSiteSelectedListener;
|
||||||
|
|
||||||
public static PinBookmarkDialog newInstance() {
|
public static PinSiteDialog newInstance() {
|
||||||
return new PinBookmarkDialog();
|
return new PinSiteDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PinBookmarkDialog() {
|
private PinSiteDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,7 +75,7 @@ class PinBookmarkDialog extends DialogFragment {
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
// All list views are styled to look the same with a global activity theme.
|
// All list views are styled to look the same with a global activity theme.
|
||||||
// If the style of the list changes, inflate it from an XML.
|
// If the style of the list changes, inflate it from an XML.
|
||||||
return inflater.inflate(R.layout.pin_bookmark_dialog, container, false);
|
return inflater.inflate(R.layout.pin_site_dialog, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -102,7 +102,7 @@ class PinBookmarkDialog extends DialogFragment {
|
||||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (mOnBookmarkSelectedListener != null) {
|
if (mOnSiteSelectedListener != null) {
|
||||||
final Cursor c = mAdapter.getCursor();
|
final Cursor c = mAdapter.getCursor();
|
||||||
if (c == null || !c.moveToPosition(position)) {
|
if (c == null || !c.moveToPosition(position)) {
|
||||||
return;
|
return;
|
||||||
|
@ -110,7 +110,7 @@ class PinBookmarkDialog extends DialogFragment {
|
||||||
|
|
||||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||||
final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
|
final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||||
mOnBookmarkSelectedListener.onBookmarkSelected(url, title);
|
mOnSiteSelectedListener.onSiteSelected(url, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss the fragment and the dialog.
|
// Dismiss the fragment and the dialog.
|
||||||
|
@ -151,8 +151,8 @@ class PinBookmarkDialog extends DialogFragment {
|
||||||
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mLoaderCallbacks, mSearchTerm);
|
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mLoaderCallbacks, mSearchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnBookmarkSelectedListener(OnBookmarkSelectedListener listener) {
|
public void setOnSiteSelectedListener(OnSiteSelectedListener listener) {
|
||||||
mOnBookmarkSelectedListener = listener;
|
mOnSiteSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SearchAdapter extends CursorAdapter {
|
private static class SearchAdapter extends CursorAdapter {
|
|
@ -1,113 +0,0 @@
|
||||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.mozilla.gecko.home;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cursor adapter holding the pinned and top bookmarks.
|
|
||||||
*/
|
|
||||||
public class TopBookmarksAdapter extends CursorAdapter {
|
|
||||||
// Cache to store the thumbnails.
|
|
||||||
private Map<String, Thumbnail> mThumbnails;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to hold the bitmap of cached thumbnails/favicons.
|
|
||||||
*/
|
|
||||||
public static class Thumbnail {
|
|
||||||
// Thumbnail or favicon.
|
|
||||||
private final boolean isThumbnail;
|
|
||||||
|
|
||||||
// Bitmap of thumbnail/favicon.
|
|
||||||
private final Bitmap bitmap;
|
|
||||||
|
|
||||||
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
|
|
||||||
this.bitmap = bitmap;
|
|
||||||
this.isThumbnail = isThumbnail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TopBookmarksAdapter(Context context, Cursor cursor) {
|
|
||||||
super(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onContentChanged() {
|
|
||||||
// Don't do anything. We don't want to regenerate every time
|
|
||||||
// our database is updated.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the thumbnails returned by the db.
|
|
||||||
*
|
|
||||||
* @param thumbnails A map of urls and their thumbnail bitmaps.
|
|
||||||
*/
|
|
||||||
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
|
|
||||||
mThumbnails = thumbnails;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void bindView(View bindView, Context context, Cursor cursor) {
|
|
||||||
String url = "";
|
|
||||||
String title = "";
|
|
||||||
boolean pinned = false;
|
|
||||||
|
|
||||||
// Cursor is already moved to required position.
|
|
||||||
if (!cursor.isAfterLast()) {
|
|
||||||
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
|
||||||
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
|
||||||
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
|
|
||||||
}
|
|
||||||
|
|
||||||
TopBookmarkItemView view = (TopBookmarkItemView) bindView;
|
|
||||||
view.setTitle(title);
|
|
||||||
view.setUrl(url);
|
|
||||||
view.setPinned(pinned);
|
|
||||||
|
|
||||||
// If there is no url, then show "add bookmark".
|
|
||||||
if (TextUtils.isEmpty(url)) {
|
|
||||||
view.displayThumbnail(R.drawable.top_bookmark_add);
|
|
||||||
} else {
|
|
||||||
// Show the thumbnail.
|
|
||||||
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
|
|
||||||
if (thumbnail == null) {
|
|
||||||
view.displayThumbnail(null);
|
|
||||||
} else if (thumbnail.isThumbnail) {
|
|
||||||
view.displayThumbnail(thumbnail.bitmap);
|
|
||||||
} else {
|
|
||||||
view.displayFavicon(thumbnail.bitmap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
|
||||||
return new TopBookmarkItemView(context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,38 +10,29 @@ import org.mozilla.gecko.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
|
||||||
import android.graphics.drawable.shapes.PathShape;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ImageView.ScaleType;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ImageView.ScaleType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view that displays the thumbnail and the title/url for a bookmark.
|
* A view that displays the thumbnail and the title/url for a top/pinned site.
|
||||||
* If the title/url is longer than the width of the view, they are faded out.
|
* If the title/url is longer than the width of the view, they are faded out.
|
||||||
* If there is no valid url, a default string is shown at 50% opacity.
|
* If there is no valid url, a default string is shown at 50% opacity.
|
||||||
* This is denoted by the empty state.
|
* This is denoted by the empty state.
|
||||||
*/
|
*/
|
||||||
public class TopBookmarkItemView extends RelativeLayout {
|
public class TopSitesGridItemView extends RelativeLayout {
|
||||||
private static final String LOGTAG = "GeckoTopBookmarkItemView";
|
private static final String LOGTAG = "GeckoTopSitesGridItemView";
|
||||||
|
|
||||||
// Empty state, to denote there is no valid url.
|
// Empty state, to denote there is no valid url.
|
||||||
private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
|
private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
|
||||||
|
|
||||||
// A Pin Drawable to denote pinned sites.
|
|
||||||
private static Drawable sPinDrawable = null;
|
|
||||||
|
|
||||||
// Child views.
|
// Child views.
|
||||||
private final TextView mTitleView;
|
private final TextView mTitleView;
|
||||||
private final ImageView mThumbnailView;
|
private final ImageView mThumbnailView;
|
||||||
private final ImageView mPinView;
|
|
||||||
|
|
||||||
// Data backing this view.
|
// Data backing this view.
|
||||||
private String mTitle;
|
private String mTitle;
|
||||||
|
@ -53,22 +44,21 @@ public class TopBookmarkItemView extends RelativeLayout {
|
||||||
// Empty state.
|
// Empty state.
|
||||||
private boolean mIsEmpty = true;
|
private boolean mIsEmpty = true;
|
||||||
|
|
||||||
public TopBookmarkItemView(Context context) {
|
public TopSitesGridItemView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TopBookmarkItemView(Context context, AttributeSet attrs) {
|
public TopSitesGridItemView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.topBookmarkItemViewStyle);
|
this(context, attrs, R.attr.topSitesGridItemViewStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TopBookmarkItemView(Context context, AttributeSet attrs, int defStyle) {
|
public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
LayoutInflater.from(context).inflate(R.layout.top_bookmark_item_view, this);
|
LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this);
|
||||||
|
|
||||||
mTitleView = (TextView) findViewById(R.id.title);
|
mTitleView = (TextView) findViewById(R.id.title);
|
||||||
mThumbnailView = (ImageView) findViewById(R.id.thumbnail);
|
mThumbnailView = (ImageView) findViewById(R.id.thumbnail);
|
||||||
mPinView = (ImageView) findViewById(R.id.pin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,7 +125,7 @@ public class TopBookmarkItemView extends RelativeLayout {
|
||||||
*/
|
*/
|
||||||
public void setPinned(boolean pinned) {
|
public void setPinned(boolean pinned) {
|
||||||
mIsPinned = pinned;
|
mIsPinned = pinned;
|
||||||
mPinView.setBackgroundDrawable(pinned ? getPinDrawable() : null);
|
mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,26 +190,4 @@ public class TopBookmarkItemView extends RelativeLayout {
|
||||||
// Refresh for state change.
|
// Refresh for state change.
|
||||||
refreshDrawableState();
|
refreshDrawableState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Drawable to be used as a pin.
|
|
||||||
*/
|
|
||||||
private Drawable getPinDrawable() {
|
|
||||||
if (sPinDrawable == null) {
|
|
||||||
int size = getResources().getDimensionPixelSize(R.dimen.top_bookmark_pinsize);
|
|
||||||
|
|
||||||
// Draw a little triangle in the upper right corner.
|
|
||||||
Path path = new Path();
|
|
||||||
path.moveTo(0, 0);
|
|
||||||
path.lineTo(size, 0);
|
|
||||||
path.lineTo(size, size);
|
|
||||||
path.close();
|
|
||||||
|
|
||||||
sPinDrawable = new ShapeDrawable(new PathShape(path, size, size));
|
|
||||||
Paint p = ((ShapeDrawable) sPinDrawable).getPaint();
|
|
||||||
p.setColor(getResources().getColor(R.color.top_bookmark_pin));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sPinDrawable;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -26,19 +26,19 @@ import android.widget.GridView;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A grid view of top bookmarks and pinned tabs.
|
* A grid view of top and pinned sites.
|
||||||
* Each cell in the grid is a TopBookmarkItemView.
|
* Each cell in the grid is a TopSitesGridItemView.
|
||||||
*/
|
*/
|
||||||
public class TopBookmarksView extends GridView {
|
public class TopSitesGridView extends GridView {
|
||||||
private static final String LOGTAG = "GeckoTopBookmarksView";
|
private static final String LOGTAG = "GeckoTopSitesGridView";
|
||||||
|
|
||||||
// Listener for pinning bookmarks.
|
// Listener for pinning sites.
|
||||||
public static interface OnPinBookmarkListener {
|
public static interface OnPinSiteListener {
|
||||||
public void onPinBookmark(int position);
|
public void onPinSite(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max number of bookmarks that needs to be shown.
|
// Max number of top sites that needs to be shown.
|
||||||
private final int mMaxBookmarks;
|
private final int mMaxSites;
|
||||||
|
|
||||||
// Number of columns to show.
|
// Number of columns to show.
|
||||||
private final int mNumColumns;
|
private final int mNumColumns;
|
||||||
|
@ -58,29 +58,29 @@ public class TopBookmarksView extends GridView {
|
||||||
// On URL open listener.
|
// On URL open listener.
|
||||||
private OnUrlOpenListener mUrlOpenListener;
|
private OnUrlOpenListener mUrlOpenListener;
|
||||||
|
|
||||||
// Pin bookmark listener.
|
// Pin site listener.
|
||||||
private OnPinBookmarkListener mPinBookmarkListener;
|
private OnPinSiteListener mPinSiteListener;
|
||||||
|
|
||||||
// Context menu info.
|
// Context menu info.
|
||||||
private TopBookmarksContextMenuInfo mContextMenuInfo;
|
private TopSitesGridContextMenuInfo mContextMenuInfo;
|
||||||
|
|
||||||
public TopBookmarksView(Context context) {
|
public TopSitesGridView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TopBookmarksView(Context context, AttributeSet attrs) {
|
public TopSitesGridView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.topBookmarksViewStyle);
|
this(context, attrs, R.attr.topSitesGridViewStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TopBookmarksView(Context context, AttributeSet attrs, int defStyle) {
|
public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
mMaxBookmarks = getResources().getInteger(R.integer.number_of_top_sites);
|
mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
|
||||||
mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
|
mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
|
||||||
setNumColumns(mNumColumns);
|
setNumColumns(mNumColumns);
|
||||||
|
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopBookmarksView, defStyle, 0);
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
|
||||||
mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_horizontalSpacing, 0x00);
|
mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
|
||||||
mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_verticalSpacing, 0x00);
|
mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,18 +94,18 @@ public class TopBookmarksView extends GridView {
|
||||||
setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
TopBookmarkItemView row = (TopBookmarkItemView) view;
|
TopSitesGridItemView row = (TopSitesGridItemView) view;
|
||||||
String url = row.getUrl();
|
String url = row.getUrl();
|
||||||
|
|
||||||
// If the url is empty, the user can pin a bookmark.
|
// If the url is empty, the user can pin a site.
|
||||||
// If not, navigate to the page given by the url.
|
// If not, navigate to the page given by the url.
|
||||||
if (!TextUtils.isEmpty(url)) {
|
if (!TextUtils.isEmpty(url)) {
|
||||||
if (mUrlOpenListener != null) {
|
if (mUrlOpenListener != null) {
|
||||||
mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
|
mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mPinBookmarkListener != null) {
|
if (mPinSiteListener != null) {
|
||||||
mPinBookmarkListener.onPinBookmark(position);
|
mPinSiteListener.onPinSite(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,8 @@ public class TopBookmarksView extends GridView {
|
||||||
@Override
|
@Override
|
||||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
|
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
|
||||||
mContextMenuInfo = new TopBookmarksContextMenuInfo(view, position, id, cursor);
|
mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id, cursor);
|
||||||
return showContextMenuForChild(TopBookmarksView.this);
|
return showContextMenuForChild(TopSitesGridView.this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ public class TopBookmarksView extends GridView {
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
|
|
||||||
mUrlOpenListener = null;
|
mUrlOpenListener = null;
|
||||||
mPinBookmarkListener = null;
|
mPinSiteListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,7 +161,7 @@ public class TopBookmarksView extends GridView {
|
||||||
ThumbnailHelper.getInstance().setThumbnailWidth(childWidth);
|
ThumbnailHelper.getInstance().setThumbnailWidth(childWidth);
|
||||||
|
|
||||||
// Get the first child from the adapter.
|
// Get the first child from the adapter.
|
||||||
final View child = new TopBookmarkItemView(getContext());
|
final View child = new TopSitesGridItemView(getContext());
|
||||||
|
|
||||||
// Set a default LayoutParams on the child, if it doesn't have one on its own.
|
// Set a default LayoutParams on the child, if it doesn't have one on its own.
|
||||||
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
|
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
|
||||||
|
@ -172,14 +172,14 @@ public class TopBookmarksView extends GridView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure the exact width of the child, and the height based on the width.
|
// Measure the exact width of the child, and the height based on the width.
|
||||||
// Note: the child (and BookmarkThumbnailView) takes care of calculating its height.
|
// Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
|
||||||
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
|
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
|
||||||
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||||
child.measure(childWidthSpec, childHeightSpec);
|
child.measure(childWidthSpec, childHeightSpec);
|
||||||
final int childHeight = child.getMeasuredHeight();
|
final int childHeight = child.getMeasuredHeight();
|
||||||
|
|
||||||
// Number of rows required to show these bookmarks.
|
// Number of rows required to show these top sites.
|
||||||
final int rows = (int) Math.ceil((double) mMaxBookmarks / mNumColumns);
|
final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
|
||||||
final int childrenHeight = childHeight * rows;
|
final int childrenHeight = childHeight * rows;
|
||||||
final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
|
final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
|
||||||
|
|
||||||
|
@ -205,24 +205,24 @@ public class TopBookmarksView extends GridView {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a pin bookmark listener to be used by this view.
|
* Set a pin site listener to be used by this view.
|
||||||
*
|
*
|
||||||
* @param listener A pin bookmark listener for this view.
|
* @param listener A pin site listener for this view.
|
||||||
*/
|
*/
|
||||||
public void setOnPinBookmarkListener(OnPinBookmarkListener listener) {
|
public void setOnPinSiteListener(OnPinSiteListener listener) {
|
||||||
mPinBookmarkListener = listener;
|
mPinSiteListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
|
* A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
|
||||||
*/
|
*/
|
||||||
public static class TopBookmarksContextMenuInfo extends AdapterContextMenuInfo {
|
public static class TopSitesGridContextMenuInfo extends AdapterContextMenuInfo {
|
||||||
|
|
||||||
public String url;
|
public String url;
|
||||||
public String title;
|
public String title;
|
||||||
public boolean isPinned;
|
public boolean isPinned;
|
||||||
|
|
||||||
public TopBookmarksContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
|
public TopSitesGridContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
|
||||||
super(targetView, position, id);
|
super(targetView, position, id);
|
||||||
|
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
|
@ -0,0 +1,768 @@
|
||||||
|
/* -*- 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.home;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.favicons.Favicons;
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.Tabs;
|
||||||
|
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||||
|
import org.mozilla.gecko.animation.PropertyAnimator.Property;
|
||||||
|
import org.mozilla.gecko.animation.ViewHelper;
|
||||||
|
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||||
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
|
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||||
|
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
|
||||||
|
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||||
|
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
|
||||||
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
|
import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
|
||||||
|
import org.mozilla.gecko.home.TopSitesGridView.OnPinSiteListener;
|
||||||
|
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnTouchListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewStub;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment that displays frecency search results in a ListView.
|
||||||
|
*/
|
||||||
|
public class TopSitesPage extends HomeFragment {
|
||||||
|
// Logging tag name
|
||||||
|
private static final String LOGTAG = "GeckoTopSitesPage";
|
||||||
|
|
||||||
|
// Cursor loader ID for the top sites
|
||||||
|
private static final int LOADER_ID_TOP_SITES = 0;
|
||||||
|
|
||||||
|
// Loader ID for thumbnails
|
||||||
|
private static final int LOADER_ID_THUMBNAILS = 1;
|
||||||
|
|
||||||
|
// Key for thumbnail urls
|
||||||
|
private static final String THUMBNAILS_URLS_KEY = "urls";
|
||||||
|
|
||||||
|
// Adapter for the list of top sites
|
||||||
|
private VisitedAdapter mListAdapter;
|
||||||
|
|
||||||
|
// Adapter for the grid of top sites
|
||||||
|
private TopSitesGridAdapter mGridAdapter;
|
||||||
|
|
||||||
|
// List of top sites
|
||||||
|
private ListView mList;
|
||||||
|
|
||||||
|
// Grid of top sites
|
||||||
|
private TopSitesGridView mGrid;
|
||||||
|
|
||||||
|
// Reference to the View to display when there are no results.
|
||||||
|
private View mEmptyView;
|
||||||
|
|
||||||
|
// Banner to show snippets.
|
||||||
|
private HomeBanner mBanner;
|
||||||
|
|
||||||
|
// Raw Y value of the last event that happened on the list view.
|
||||||
|
private float mListTouchY = -1;
|
||||||
|
|
||||||
|
// Scrolling direction of the banner.
|
||||||
|
private boolean mSnapBannerToTop;
|
||||||
|
|
||||||
|
// Callbacks used for the search and favicon cursor loaders
|
||||||
|
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||||
|
|
||||||
|
// Callback for thumbnail loader
|
||||||
|
private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
|
||||||
|
|
||||||
|
// Listener for pinning sites
|
||||||
|
private PinSiteListener mPinSiteListener;
|
||||||
|
|
||||||
|
// On URL open listener
|
||||||
|
private OnUrlOpenListener mUrlOpenListener;
|
||||||
|
|
||||||
|
// Max number of entries shown in the grid from the cursor.
|
||||||
|
private int mMaxGridEntries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to hold the bitmap of cached thumbnails/favicons.
|
||||||
|
*/
|
||||||
|
public static class Thumbnail {
|
||||||
|
// Thumbnail or favicon.
|
||||||
|
private final boolean isThumbnail;
|
||||||
|
|
||||||
|
// Bitmap of thumbnail/favicon.
|
||||||
|
private final Bitmap bitmap;
|
||||||
|
|
||||||
|
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
|
||||||
|
this.bitmap = bitmap;
|
||||||
|
this.isThumbnail = isThumbnail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TopSitesPage newInstance() {
|
||||||
|
return new TopSitesPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TopSitesPage() {
|
||||||
|
mUrlOpenListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement HomePager.OnUrlOpenListener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetach() {
|
||||||
|
super.onDetach();
|
||||||
|
|
||||||
|
mUrlOpenListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
final View view = inflater.inflate(R.layout.home_top_sites_page, container, false);
|
||||||
|
|
||||||
|
mList = (HomeListView) view.findViewById(R.id.list);
|
||||||
|
|
||||||
|
mGrid = new TopSitesGridView(getActivity());
|
||||||
|
mList.addHeaderView(mGrid);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
mPinSiteListener = new PinSiteListener();
|
||||||
|
|
||||||
|
mList.setTag(HomePager.LIST_TAG_TOP_SITES);
|
||||||
|
mList.setHeaderDividersEnabled(false);
|
||||||
|
|
||||||
|
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
final ListView list = (ListView) parent;
|
||||||
|
final int headerCount = list.getHeaderViewsCount();
|
||||||
|
if (position < headerCount) {
|
||||||
|
// The click is on a header, don't do anything.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute position for the adapter.
|
||||||
|
position += (mGridAdapter.getCount() - headerCount);
|
||||||
|
|
||||||
|
final Cursor c = mListAdapter.getCursor();
|
||||||
|
if (c == null || !c.moveToPosition(position)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||||
|
|
||||||
|
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||||
|
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mGrid.setOnUrlOpenListener(mUrlOpenListener);
|
||||||
|
mGrid.setOnPinSiteListener(mPinSiteListener);
|
||||||
|
|
||||||
|
registerForContextMenu(mList);
|
||||||
|
registerForContextMenu(mGrid);
|
||||||
|
|
||||||
|
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
|
||||||
|
mList.setOnTouchListener(new OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
TopSitesPage.this.handleListTouchEvent(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
mList = null;
|
||||||
|
mGrid = null;
|
||||||
|
mEmptyView = null;
|
||||||
|
mListAdapter = null;
|
||||||
|
mGridAdapter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
|
||||||
|
// Detach and reattach the fragment as the layout changes.
|
||||||
|
if (isVisible()) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.detach(this)
|
||||||
|
.attach(this)
|
||||||
|
.commitAllowingStateLoss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
|
// Setup the top sites grid adapter.
|
||||||
|
mGridAdapter = new TopSitesGridAdapter(activity, null);
|
||||||
|
mGrid.setAdapter(mGridAdapter);
|
||||||
|
|
||||||
|
// Setup the top sites list adapter.
|
||||||
|
mListAdapter = new VisitedAdapter(activity, null);
|
||||||
|
mList.setAdapter(mListAdapter);
|
||||||
|
|
||||||
|
// Create callbacks before the initial loader is started
|
||||||
|
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
||||||
|
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
|
||||||
|
loadIfVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
|
||||||
|
if (menuInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HomeFragment will handle the default case.
|
||||||
|
if (menuInfo instanceof HomeContextMenuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, view, menuInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuInflater inflater = new MenuInflater(view.getContext());
|
||||||
|
inflater.inflate(R.menu.top_sites_contextmenu, menu);
|
||||||
|
|
||||||
|
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
|
||||||
|
menu.setHeaderTitle(info.getDisplayTitle());
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(info.url)) {
|
||||||
|
if (info.isPinned) {
|
||||||
|
menu.findItem(R.id.top_sites_pin).setVisible(false);
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.top_sites_unpin).setVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.top_sites_open_new_tab).setVisible(false);
|
||||||
|
menu.findItem(R.id.top_sites_open_private_tab).setVisible(false);
|
||||||
|
menu.findItem(R.id.top_sites_pin).setVisible(false);
|
||||||
|
menu.findItem(R.id.top_sites_unpin).setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
|
ContextMenuInfo menuInfo = item.getMenuInfo();
|
||||||
|
|
||||||
|
// HomeFragment will handle the default case.
|
||||||
|
if (menuInfo == null || !(menuInfo instanceof TopSitesGridContextMenuInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
|
final int itemId = item.getItemId();
|
||||||
|
if (itemId == R.id.top_sites_open_new_tab || itemId == R.id.top_sites_open_private_tab) {
|
||||||
|
if (info.url == null) {
|
||||||
|
Log.e(LOGTAG, "Can't open in new tab because URL is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
|
||||||
|
if (item.getItemId() == R.id.top_sites_open_private_tab)
|
||||||
|
flags |= Tabs.LOADURL_PRIVATE;
|
||||||
|
|
||||||
|
Tabs.getInstance().loadUrl(info.url, flags);
|
||||||
|
Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.top_sites_pin) {
|
||||||
|
final String url = info.url;
|
||||||
|
final String title = info.title;
|
||||||
|
final int position = info.position;
|
||||||
|
final Context context = getActivity().getApplicationContext();
|
||||||
|
|
||||||
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.top_sites_unpin) {
|
||||||
|
final int position = info.position;
|
||||||
|
final Context context = getActivity().getApplicationContext();
|
||||||
|
|
||||||
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BrowserDB.unpinSite(context.getContentResolver(), position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.top_sites_edit) {
|
||||||
|
mPinSiteListener.onPinSite(info.position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
getLoaderManager().initLoader(LOADER_ID_TOP_SITES, null, mCursorLoaderCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for pinning sites.
|
||||||
|
*/
|
||||||
|
private class PinSiteListener implements OnPinSiteListener,
|
||||||
|
OnSiteSelectedListener {
|
||||||
|
// Tag for the PinSiteDialog fragment.
|
||||||
|
private static final String TAG_PIN_SITE = "pin_site";
|
||||||
|
|
||||||
|
// Position of the pin.
|
||||||
|
private int mPosition;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPinSite(int position) {
|
||||||
|
mPosition = position;
|
||||||
|
|
||||||
|
final FragmentManager manager = getActivity().getSupportFragmentManager();
|
||||||
|
PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE);
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = PinSiteDialog.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setOnSiteSelectedListener(this);
|
||||||
|
dialog.show(manager, TAG_PIN_SITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSiteSelected(final String url, final String title) {
|
||||||
|
final int position = mPosition;
|
||||||
|
final Context context = getActivity().getApplicationContext();
|
||||||
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleListTouchEvent(MotionEvent event) {
|
||||||
|
// Ignore the event if the banner is hidden for this session.
|
||||||
|
if (mBanner.isDismissed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.getActionMasked()) {
|
||||||
|
case MotionEvent.ACTION_DOWN: {
|
||||||
|
mListTouchY = event.getRawY();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
// There is a chance that we won't receive ACTION_DOWN, if the touch event
|
||||||
|
// actually started on the Grid instead of the List. Treat this as first event.
|
||||||
|
if (mListTouchY == -1) {
|
||||||
|
mListTouchY = event.getRawY();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float curY = event.getRawY();
|
||||||
|
final float delta = mListTouchY - curY;
|
||||||
|
mSnapBannerToTop = (delta > 0.0f) ? false : true;
|
||||||
|
|
||||||
|
final float height = mBanner.getHeight();
|
||||||
|
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
|
||||||
|
|
||||||
|
// Clamp the values to be between 0 and height.
|
||||||
|
if (newTranslationY < 0.0f) {
|
||||||
|
newTranslationY = 0.0f;
|
||||||
|
} else if (newTranslationY > height) {
|
||||||
|
newTranslationY = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewHelper.setTranslationY(mBanner, newTranslationY);
|
||||||
|
mListTouchY = curY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL: {
|
||||||
|
mListTouchY = -1;
|
||||||
|
final float y = ViewHelper.getTranslationY(mBanner);
|
||||||
|
final float height = mBanner.getHeight();
|
||||||
|
if (y > 0.0f && y < height) {
|
||||||
|
final PropertyAnimator animator = new PropertyAnimator(100);
|
||||||
|
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUiFromCursor(Cursor c) {
|
||||||
|
mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
|
||||||
|
|
||||||
|
if (c != null && c.getCount() > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEmptyView == null) {
|
||||||
|
// Set empty page view. We delay this so that the empty view won't flash.
|
||||||
|
ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||||
|
mEmptyView = emptyViewStub.inflate();
|
||||||
|
|
||||||
|
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
||||||
|
emptyIcon.setImageResource(R.drawable.icon_most_visited_empty);
|
||||||
|
|
||||||
|
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
||||||
|
emptyText.setText(R.string.home_most_visited_empty);
|
||||||
|
|
||||||
|
mList.setEmptyView(mEmptyView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TopSitesLoader extends SimpleCursorLoader {
|
||||||
|
// Max number of search results
|
||||||
|
private static final int SEARCH_LIMIT = 30;
|
||||||
|
private int mMaxGridEntries;
|
||||||
|
|
||||||
|
public TopSitesLoader(Context context) {
|
||||||
|
super(context);
|
||||||
|
mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor loadCursor() {
|
||||||
|
return BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VisitedAdapter extends CursorAdapter {
|
||||||
|
public VisitedAdapter(Context context, Cursor cursor) {
|
||||||
|
super(context, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return Math.max(0, super.getCount() - mMaxGridEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return super.getItem(position + mMaxGridEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
final int position = cursor.getPosition();
|
||||||
|
cursor.moveToPosition(position + mMaxGridEntries);
|
||||||
|
|
||||||
|
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||||
|
row.updateFromCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
return LayoutInflater.from(context).inflate(R.layout.bookmark_item_row, parent, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TopSitesGridAdapter extends CursorAdapter {
|
||||||
|
// Cache to store the thumbnails.
|
||||||
|
private Map<String, Thumbnail> mThumbnails;
|
||||||
|
|
||||||
|
public TopSitesGridAdapter(Context context, Cursor cursor) {
|
||||||
|
super(context, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return Math.min(mMaxGridEntries, super.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onContentChanged() {
|
||||||
|
// Don't do anything. We don't want to regenerate every time
|
||||||
|
// our database is updated.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the thumbnails returned by the db.
|
||||||
|
*
|
||||||
|
* @param thumbnails A map of urls and their thumbnail bitmaps.
|
||||||
|
*/
|
||||||
|
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
|
||||||
|
mThumbnails = thumbnails;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View bindView, Context context, Cursor cursor) {
|
||||||
|
String url = "";
|
||||||
|
String title = "";
|
||||||
|
boolean pinned = false;
|
||||||
|
|
||||||
|
// Cursor is already moved to required position.
|
||||||
|
if (!cursor.isAfterLast()) {
|
||||||
|
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
||||||
|
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||||
|
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
|
||||||
|
}
|
||||||
|
|
||||||
|
TopSitesGridItemView view = (TopSitesGridItemView) bindView;
|
||||||
|
view.setTitle(title);
|
||||||
|
view.setUrl(url);
|
||||||
|
view.setPinned(pinned);
|
||||||
|
|
||||||
|
// If there is no url, then show "add bookmark".
|
||||||
|
if (TextUtils.isEmpty(url)) {
|
||||||
|
view.displayThumbnail(R.drawable.top_site_add);
|
||||||
|
} else {
|
||||||
|
// Show the thumbnail.
|
||||||
|
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
|
||||||
|
if (thumbnail == null) {
|
||||||
|
view.displayThumbnail(null);
|
||||||
|
} else if (thumbnail.isThumbnail) {
|
||||||
|
view.displayThumbnail(thumbnail.bitmap);
|
||||||
|
} else {
|
||||||
|
view.displayFavicon(thumbnail.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
return new TopSitesGridItemView(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new TopSitesLoader(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||||
|
mListAdapter.swapCursor(c);
|
||||||
|
mGridAdapter.swapCursor(c);
|
||||||
|
updateUiFromCursor(c);
|
||||||
|
|
||||||
|
// Load the thumbnails.
|
||||||
|
if (c.getCount() > 0 && c.moveToFirst()) {
|
||||||
|
final ArrayList<String> urls = new ArrayList<String>();
|
||||||
|
do {
|
||||||
|
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||||
|
urls.add(url);
|
||||||
|
} while (c.moveToNext());
|
||||||
|
|
||||||
|
if (urls.size() > 0) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
|
||||||
|
getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
if (mListAdapter != null) {
|
||||||
|
mListAdapter.swapCursor(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mGridAdapter != null) {
|
||||||
|
mGridAdapter.swapCursor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AsyncTaskLoader to load the thumbnails from a cursor.
|
||||||
|
*/
|
||||||
|
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
|
||||||
|
private Map<String, Thumbnail> mThumbnails;
|
||||||
|
private ArrayList<String> mUrls;
|
||||||
|
|
||||||
|
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
|
||||||
|
super(context);
|
||||||
|
mUrls = urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Thumbnail> loadInBackground() {
|
||||||
|
if (mUrls == null || mUrls.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
|
||||||
|
|
||||||
|
// Query the DB for thumbnails.
|
||||||
|
final ContentResolver cr = getContext().getContentResolver();
|
||||||
|
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
// Try to get the thumbnail, if cursor is valid.
|
||||||
|
String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
|
||||||
|
final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
|
||||||
|
final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
|
||||||
|
|
||||||
|
if (bitmap != null) {
|
||||||
|
thumbnails.put(url, new Thumbnail(bitmap, true));
|
||||||
|
}
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the DB for favicons for the urls without thumbnails.
|
||||||
|
for (String url : mUrls) {
|
||||||
|
if (!thumbnails.containsKey(url)) {
|
||||||
|
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
|
||||||
|
if (bitmap != null) {
|
||||||
|
// Favicons.scaleImage can return several different size favicons,
|
||||||
|
// but will at least prevent this from being too large.
|
||||||
|
thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(Map<String, Thumbnail> thumbnails) {
|
||||||
|
if (isReset()) {
|
||||||
|
mThumbnails = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mThumbnails = thumbnails;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
super.deliverResult(thumbnails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mThumbnails != null) {
|
||||||
|
deliverResult(mThumbnails);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || mThumbnails == null) {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
cancelLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(Map<String, Thumbnail> thumbnails) {
|
||||||
|
mThumbnails = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
super.onReset();
|
||||||
|
|
||||||
|
// Ensure the loader is stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
mThumbnails = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loader callbacks for the thumbnails on TopSitesGridView.
|
||||||
|
*/
|
||||||
|
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
|
||||||
|
@Override
|
||||||
|
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
|
||||||
|
if (mGridAdapter != null) {
|
||||||
|
mGridAdapter.updateThumbnails(thumbnails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
|
||||||
|
if (mGridAdapter != null) {
|
||||||
|
mGridAdapter.updateThumbnails(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,10 @@ import android.util.AttributeSet;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A height constrained ImageView to show thumbnails of top bookmarks.
|
* A height constrained ImageView to show thumbnails of top and pinned sites.
|
||||||
*/
|
*/
|
||||||
public class BookmarkThumbnailView extends ImageView {
|
public class TopSitesThumbnailView extends ImageView {
|
||||||
private static final String LOGTAG = "GeckoBookmarkThumbnailView";
|
private static final String LOGTAG = "GeckoTopSitesThumbnailView";
|
||||||
|
|
||||||
// 27.34% opacity filter for the dominant color.
|
// 27.34% opacity filter for the dominant color.
|
||||||
private static final int COLOR_FILTER = 0x46FFFFFF;
|
private static final int COLOR_FILTER = 0x46FFFFFF;
|
||||||
|
@ -41,18 +41,18 @@ public class BookmarkThumbnailView extends ImageView {
|
||||||
sBorderPaint.setStyle(Paint.Style.STROKE);
|
sBorderPaint.setStyle(Paint.Style.STROKE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookmarkThumbnailView(Context context) {
|
public TopSitesThumbnailView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
|
||||||
// A border will be drawn if needed.
|
// A border will be drawn if needed.
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookmarkThumbnailView(Context context, AttributeSet attrs) {
|
public TopSitesThumbnailView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.bookmarkThumbnailViewStyle);
|
this(context, attrs, R.attr.topSitesThumbnailViewStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookmarkThumbnailView(Context context, AttributeSet attrs, int defStyle) {
|
public TopSitesThumbnailView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public class BookmarkThumbnailView extends ImageView {
|
||||||
@Override
|
@Override
|
||||||
public void setBackgroundColor(int color) {
|
public void setBackgroundColor(int color) {
|
||||||
int colorFilter = color == 0 ? DEFAULT_COLOR : color & COLOR_FILTER;
|
int colorFilter = color == 0 ? DEFAULT_COLOR : color & COLOR_FILTER;
|
||||||
Drawable drawable = getResources().getDrawable(R.drawable.bookmark_thumbnail_bg);
|
Drawable drawable = getResources().getDrawable(R.drawable.top_sites_thumbnail_bg);
|
||||||
drawable.setColorFilter(colorFilter, Mode.SRC_ATOP);
|
drawable.setColorFilter(colorFilter, Mode.SRC_ATOP);
|
||||||
setBackgroundDrawable(drawable);
|
setBackgroundDrawable(drawable);
|
||||||
}
|
}
|
|
@ -225,9 +225,9 @@ size. -->
|
||||||
<!ENTITY contextmenu_edit_bookmark "Edit">
|
<!ENTITY contextmenu_edit_bookmark "Edit">
|
||||||
<!ENTITY contextmenu_subscribe "Subscribe to Page">
|
<!ENTITY contextmenu_subscribe "Subscribe to Page">
|
||||||
<!ENTITY contextmenu_site_settings "Edit Site Settings">
|
<!ENTITY contextmenu_site_settings "Edit Site Settings">
|
||||||
<!ENTITY contextmenu_top_bookmarks_edit "Edit">
|
<!ENTITY contextmenu_top_sites_edit "Edit">
|
||||||
<!ENTITY contextmenu_top_bookmarks_pin "Pin Site">
|
<!ENTITY contextmenu_top_sites_pin "Pin Site">
|
||||||
<!ENTITY contextmenu_top_bookmarks_unpin "Unpin Site">
|
<!ENTITY contextmenu_top_sites_unpin "Unpin Site">
|
||||||
|
|
||||||
<!ENTITY pref_titlebar_mode "Title bar">
|
<!ENTITY pref_titlebar_mode "Title bar">
|
||||||
<!ENTITY pref_titlebar_mode_title "Show page title">
|
<!ENTITY pref_titlebar_mode_title "Show page title">
|
||||||
|
@ -270,6 +270,7 @@ size. -->
|
||||||
<!ENTITY button_set "Set">
|
<!ENTITY button_set "Set">
|
||||||
<!ENTITY button_clear "Clear">
|
<!ENTITY button_clear "Clear">
|
||||||
|
|
||||||
|
<!ENTITY home_top_sites_title "Top Sites">
|
||||||
<!ENTITY home_history_title "History">
|
<!ENTITY home_history_title "History">
|
||||||
<!ENTITY home_last_tabs_title "Tabs from last time">
|
<!ENTITY home_last_tabs_title "Tabs from last time">
|
||||||
<!ENTITY home_last_tabs_open "Open all tabs from last time">
|
<!ENTITY home_last_tabs_open "Open all tabs from last time">
|
||||||
|
@ -288,7 +289,7 @@ size. -->
|
||||||
<!ENTITY home_reading_list_hint_accessible "TIP: Save articles to your reading list by long pressing the reader mode button when it appears in the title bar.">
|
<!ENTITY home_reading_list_hint_accessible "TIP: Save articles to your reading list by long pressing the reader mode button when it appears in the title bar.">
|
||||||
|
|
||||||
<!ENTITY home_most_visited_empty "Websites you visit most frequently show up here.">
|
<!ENTITY home_most_visited_empty "Websites you visit most frequently show up here.">
|
||||||
<!ENTITY pin_bookmark_dialog_hint "Enter a search keyword">
|
<!ENTITY pin_site_dialog_hint "Enter a search keyword">
|
||||||
|
|
||||||
<!ENTITY filepicker_title "Choose File">
|
<!ENTITY filepicker_title "Choose File">
|
||||||
<!ENTITY filepicker_audio_title "Choose or record a sound">
|
<!ENTITY filepicker_audio_title "Choose or record a sound">
|
||||||
|
|
После Ширина: | Высота: | Размер: 249 B |
До Ширина: | Высота: | Размер: 151 B После Ширина: | Высота: | Размер: 151 B |
После Ширина: | Высота: | Размер: 192 B |
До Ширина: | Высота: | Размер: 137 B После Ширина: | Высота: | Размер: 137 B |
После Ширина: | Высота: | Размер: 285 B |
До Ширина: | Высота: | Размер: 156 B После Ширина: | Высота: | Размер: 156 B |
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<shape android:shape="rectangle" >
|
<shape android:shape="rectangle" >
|
||||||
<stroke android:width="1px"
|
<stroke android:width="1px"
|
||||||
android:color="@color/doorhanger_divider_dark" />
|
android:color="@color/doorhanger_divider_light" />
|
||||||
|
|
||||||
<solid android:color="#00000000" />
|
<solid android:color="#00000000" />
|
||||||
</shape>
|
</shape>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
android:layout="@layout/home_history_tabs_indicator"
|
android:layout="@layout/home_history_tabs_indicator"
|
||||||
gecko:display="text"/>
|
gecko:display="text"/>
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/visited_page_container"
|
<FrameLayout android:id="@+id/history_page_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
android:layout="@layout/home_history_tabs_indicator"
|
android:layout="@layout/home_history_tabs_indicator"
|
||||||
gecko:display="text"/>
|
gecko:display="text"/>
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/visited_page_container"
|
<FrameLayout android:id="@+id/history_page_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
|
@ -12,15 +12,4 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
|
|
||||||
style="@style/Widget.HomeBanner"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="@dimen/home_banner_height"
|
|
||||||
android:background="@drawable/home_banner"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
<TextView android:id="@+id/title"
|
<TextView android:id="@+id/title"
|
||||||
style="@style/Widget.Home.HistoryTabIndicator"
|
style="@style/Widget.Home.HistoryPageTitle"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<org.mozilla.gecko.home.HomeListView
|
<org.mozilla.gecko.home.HomeListView
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/visited_page_container"
|
<FrameLayout android:id="@+id/history_page_container"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include layout="@layout/home_history_list"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?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/. -->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ViewStub android:id="@+id/home_empty_view_stub"
|
||||||
|
android:layout="@layout/home_empty_page"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
<org.mozilla.gecko.home.HomeListView
|
||||||
|
android:id="@+id/list"
|
||||||
|
style="@style/Widget.TopSitesListView"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
|
||||||
|
style="@style/Widget.HomeBanner"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="@dimen/home_banner_height"
|
||||||
|
android:background="@drawable/home_banner"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -19,7 +19,7 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:padding="6dip"
|
android:padding="6dip"
|
||||||
android:hint="@string/pin_bookmark_dialog_hint"
|
android:hint="@string/pin_site_dialog_hint"
|
||||||
android:background="@drawable/url_bar_entry"
|
android:background="@drawable/url_bar_entry"
|
||||||
android:textColor="@color/url_bar_title"
|
android:textColor="@color/url_bar_title"
|
||||||
android:textColorHint="@color/url_bar_title_hint"
|
android:textColorHint="@color/url_bar_title_hint"
|
|
@ -6,7 +6,7 @@
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<org.mozilla.gecko.home.BookmarkThumbnailView
|
<org.mozilla.gecko.home.TopSitesThumbnailView
|
||||||
android:id="@+id/thumbnail"
|
android:id="@+id/thumbnail"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -14,18 +14,12 @@
|
||||||
|
|
||||||
<org.mozilla.gecko.home.FadedTextView
|
<org.mozilla.gecko.home.FadedTextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
style="@style/Widget.TopBookmarkItemTitle"
|
style="@style/Widget.TopSitesGridItemTitle"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/thumbnail"
|
android:layout_below="@id/thumbnail"
|
||||||
android:duplicateParentState="true"
|
android:duplicateParentState="true"
|
||||||
|
android:drawablePadding="4dip"
|
||||||
gecko:fadeWidth="20dip"/>
|
gecko:fadeWidth="20dip"/>
|
||||||
|
|
||||||
<ImageView android:id="@+id/pin"
|
|
||||||
style="@style/Widget.TopBookmarkItemPin"
|
|
||||||
android:layout_width="@dimen/top_bookmark_pinsize"
|
|
||||||
android:layout_height="@dimen/top_bookmark_pinsize"
|
|
||||||
android:layout_alignTop="@id/thumbnail"
|
|
||||||
android:layout_alignRight="@id/thumbnail"/>
|
|
||||||
|
|
||||||
</merge>
|
</merge>
|
|
@ -5,19 +5,19 @@
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:id="@+id/top_bookmarks_open_new_tab"
|
<item android:id="@+id/top_sites_open_new_tab"
|
||||||
android:title="@string/contextmenu_open_new_tab"/>
|
android:title="@string/contextmenu_open_new_tab"/>
|
||||||
|
|
||||||
<item android:id="@+id/top_bookmarks_open_private_tab"
|
<item android:id="@+id/top_sites_open_private_tab"
|
||||||
android:title="@string/contextmenu_open_private_tab"/>
|
android:title="@string/contextmenu_open_private_tab"/>
|
||||||
|
|
||||||
<item android:id="@+id/top_bookmarks_edit"
|
<item android:id="@+id/top_sites_edit"
|
||||||
android:title="@string/contextmenu_top_bookmarks_edit"/>
|
android:title="@string/contextmenu_top_sites_edit"/>
|
||||||
|
|
||||||
<item android:id="@+id/top_bookmarks_pin"
|
<item android:id="@+id/top_sites_pin"
|
||||||
android:title="@string/contextmenu_top_bookmarks_pin"/>
|
android:title="@string/contextmenu_top_sites_pin"/>
|
||||||
|
|
||||||
<item android:id="@+id/top_bookmarks_unpin"
|
<item android:id="@+id/top_sites_unpin"
|
||||||
android:title="@string/contextmenu_top_bookmarks_unpin"/>
|
android:title="@string/contextmenu_top_sites_unpin"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -21,15 +21,16 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
|
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
|
||||||
|
<item name="android:paddingTop">30dp</item>
|
||||||
<item name="android:paddingLeft">120dp</item>
|
<item name="android:paddingLeft">120dp</item>
|
||||||
<item name="android:paddingRight">120dp</item>
|
<item name="android:paddingRight">120dp</item>
|
||||||
<item name="android:scrollbarStyle">outsideOverlay</item>
|
<item name="android:scrollbarStyle">outsideOverlay</item>
|
||||||
|
<item name="topDivider">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
|
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
|
||||||
<item name="android:paddingLeft">55dp</item>
|
<item name="android:paddingLeft">55dp</item>
|
||||||
<item name="android:paddingRight">55dp</item>
|
<item name="android:paddingRight">55dp</item>
|
||||||
<item name="android:paddingTop">30dp</item>
|
|
||||||
<item name="android:paddingBottom">30dp</item>
|
<item name="android:paddingBottom">30dp</item>
|
||||||
<item name="android:horizontalSpacing">20dp</item>
|
<item name="android:horizontalSpacing">20dp</item>
|
||||||
<item name="android:verticalSpacing">20dp</item>
|
<item name="android:verticalSpacing">20dp</item>
|
||||||
|
|
|
@ -62,23 +62,36 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
|
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
|
||||||
|
<item name="android:paddingTop">30dp</item>
|
||||||
<item name="android:paddingLeft">32dp</item>
|
<item name="android:paddingLeft">32dp</item>
|
||||||
<item name="android:paddingRight">32dp</item>
|
<item name="android:paddingRight">32dp</item>
|
||||||
<item name="android:scrollbarStyle">outsideOverlay</item>
|
<item name="android:scrollbarStyle">outsideOverlay</item>
|
||||||
|
<item name="topDivider">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
|
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
|
||||||
<item name="android:paddingLeft">5dp</item>
|
<item name="android:paddingLeft">5dp</item>
|
||||||
<item name="android:paddingRight">5dp</item>
|
<item name="android:paddingRight">5dp</item>
|
||||||
<item name="android:paddingTop">30dp</item>
|
|
||||||
<item name="android:paddingBottom">30dp</item>
|
<item name="android:paddingBottom">30dp</item>
|
||||||
<item name="android:horizontalSpacing">10dp</item>
|
<item name="android:horizontalSpacing">10dp</item>
|
||||||
<item name="android:verticalSpacing">10dp</item>
|
<item name="android:verticalSpacing">10dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView">
|
<style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
|
||||||
<item name="android:paddingTop">30dp</item>
|
<item name="topDivider">false</item>
|
||||||
<item name="topDivider">true</item>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
|
||||||
|
|
||||||
|
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator">
|
||||||
|
<item name="android:layout_marginLeft">32dp</item>
|
||||||
|
<item name="android:layout_marginRight">32dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Home.HistoryListView">
|
||||||
|
<item name="android:paddingLeft">32dp</item>
|
||||||
|
<item name="android:paddingRight">32dp</item>
|
||||||
|
<item name="android:scrollbarStyle">outsideOverlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.HomeBanner">
|
<style name="Widget.HomeBanner">
|
||||||
|
|
|
@ -43,9 +43,9 @@
|
||||||
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
|
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
|
||||||
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
|
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
|
||||||
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
|
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
|
||||||
<item name="bookmarkThumbnailViewStyle">@style/Widget.BookmarkThumbnailView</item>
|
<item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
|
||||||
<item name="topBookmarkItemViewStyle">@style/Widget.TopBookmarkItemView</item>
|
<item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
|
||||||
<item name="topBookmarksViewStyle">@style/Widget.TopBookmarksView</item>
|
<item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
|
||||||
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
|
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
|
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
|
||||||
<item name="android:paddingLeft">55dp</item>
|
<item name="android:paddingLeft">55dp</item>
|
||||||
<item name="android:paddingRight">55dp</item>
|
<item name="android:paddingRight">55dp</item>
|
||||||
<item name="android:paddingTop">30dp</item>
|
|
||||||
<item name="android:paddingBottom">30dp</item>
|
<item name="android:paddingBottom">30dp</item>
|
||||||
<item name="android:horizontalSpacing">56dp</item>
|
<item name="android:horizontalSpacing">56dp</item>
|
||||||
<item name="android:verticalSpacing">20dp</item>
|
<item name="android:verticalSpacing">20dp</item>
|
||||||
|
|
|
@ -20,14 +20,14 @@
|
||||||
<!-- Default style for the BookmarksListView -->
|
<!-- Default style for the BookmarksListView -->
|
||||||
<attr name="bookmarksListViewStyle" format="reference" />
|
<attr name="bookmarksListViewStyle" format="reference" />
|
||||||
|
|
||||||
<!-- Default style for the BookmarkThumbnailView -->
|
<!-- Default style for the TopSitesGridItemView -->
|
||||||
<attr name="bookmarkThumbnailViewStyle" format="reference" />
|
<attr name="topSitesGridItemViewStyle" format="reference" />
|
||||||
|
|
||||||
<!-- Default style for the TopBookmarkItemView -->
|
<!-- Default style for the TopSitesGridView -->
|
||||||
<attr name="topBookmarkItemViewStyle" format="reference" />
|
<attr name="topSitesGridViewStyle" format="reference" />
|
||||||
|
|
||||||
<!-- Default style for the TopBookmarksView -->
|
<!-- Default style for the TopSitesThumbnailView -->
|
||||||
<attr name="topBookmarksViewStyle" format="reference" />
|
<attr name="topSitesThumbnailViewStyle" format="reference" />
|
||||||
|
|
||||||
<!-- Default style for the HomeListView -->
|
<!-- Default style for the HomeListView -->
|
||||||
<attr name="homeListViewStyle" format="reference" />
|
<attr name="homeListViewStyle" format="reference" />
|
||||||
|
@ -212,7 +212,7 @@
|
||||||
</attr>
|
</attr>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="TopBookmarksView">
|
<declare-styleable name="TopSitesGridView">
|
||||||
<attr name="android:horizontalSpacing"/>
|
<attr name="android:horizontalSpacing"/>
|
||||||
<attr name="android:verticalSpacing"/>
|
<attr name="android:verticalSpacing"/>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
|
@ -75,7 +75,6 @@
|
||||||
<color name="suggestion_primary">#dddddd</color>
|
<color name="suggestion_primary">#dddddd</color>
|
||||||
<color name="suggestion_pressed">#bbbbbb</color>
|
<color name="suggestion_pressed">#bbbbbb</color>
|
||||||
<color name="tab_row_pressed">#4D000000</color>
|
<color name="tab_row_pressed">#4D000000</color>
|
||||||
<color name="top_bookmark_pin">#55000000</color>
|
|
||||||
<color name="dialogtitle_textcolor">#ffffff</color>
|
<color name="dialogtitle_textcolor">#ffffff</color>
|
||||||
|
|
||||||
<color name="textbox_background">#FFF</color>
|
<color name="textbox_background">#FFF</color>
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
<dimen name="text_selection_handle_width">47dp</dimen>
|
<dimen name="text_selection_handle_width">47dp</dimen>
|
||||||
<dimen name="text_selection_handle_height">58dp</dimen>
|
<dimen name="text_selection_handle_height">58dp</dimen>
|
||||||
<dimen name="text_selection_handle_shadow">11dp</dimen>
|
<dimen name="text_selection_handle_shadow">11dp</dimen>
|
||||||
<dimen name="top_bookmark_pinsize">20dp</dimen>
|
|
||||||
<dimen name="validation_message_height">50dp</dimen>
|
<dimen name="validation_message_height">50dp</dimen>
|
||||||
<dimen name="validation_message_margin_top">6dp</dimen>
|
<dimen name="validation_message_margin_top">6dp</dimen>
|
||||||
<dimen name="forward_default_offset">-13dip</dimen>
|
<dimen name="forward_default_offset">-13dip</dimen>
|
||||||
|
@ -82,8 +81,8 @@
|
||||||
<!-- We need to maintain height for the tab widget on History Page
|
<!-- We need to maintain height for the tab widget on History Page
|
||||||
since android does not add footer/header divider height to its
|
since android does not add footer/header divider height to its
|
||||||
calculation for wrap_content in LinearLayout.
|
calculation for wrap_content in LinearLayout.
|
||||||
50dp * 3 Views + 30dp padding + 4dp dividers-->
|
50dp * 2 Views + 30dp padding + 4dp dividers-->
|
||||||
<dimen name="history_tab_widget_height">184dp</dimen>
|
<dimen name="history_tab_widget_height">134dp</dimen>
|
||||||
|
|
||||||
<!-- PageActionButtons dimensions -->
|
<!-- PageActionButtons dimensions -->
|
||||||
<dimen name="page_action_button_width">32dp</dimen>
|
<dimen name="page_action_button_width">32dp</dimen>
|
||||||
|
|
|
@ -117,13 +117,13 @@
|
||||||
<item name="android:drawableLeft">@drawable/bookmark_folder</item>
|
<item name="android:drawableLeft">@drawable/bookmark_folder</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
|
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
|
||||||
<item name="android:padding">7dp</item>
|
<item name="android:padding">7dp</item>
|
||||||
<item name="android:horizontalSpacing">0dp</item>
|
<item name="android:horizontalSpacing">0dp</item>
|
||||||
<item name="android:verticalSpacing">7dp</item>
|
<item name="android:verticalSpacing">7dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarkItemView">
|
<style name="Widget.TopSitesGridItemView">
|
||||||
<item name="android:layout_width">fill_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">fill_parent</item>
|
<item name="android:layout_height">fill_parent</item>
|
||||||
<item name="android:padding">5dip</item>
|
<item name="android:padding">5dip</item>
|
||||||
|
@ -134,19 +134,19 @@
|
||||||
|
|
||||||
<style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
|
<style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
|
||||||
|
|
||||||
<style name="Widget.BookmarkThumbnailView">
|
<style name="Widget.TopSitesThumbnailView">
|
||||||
<item name="android:padding">0dip</item>
|
<item name="android:padding">0dip</item>
|
||||||
<item name="android:scaleType">centerCrop</item>
|
<item name="android:scaleType">centerCrop</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarkItemPin">
|
<style name="Widget.TopSitesGridItemPin">
|
||||||
<item name="android:minWidth">30dip</item>
|
<item name="android:minWidth">30dip</item>
|
||||||
<item name="android:minHeight">30dip</item>
|
<item name="android:minHeight">30dip</item>
|
||||||
<item name="android:padding">0dip</item>
|
<item name="android:padding">0dip</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.TopBookmarkItemTitle">
|
<style name="Widget.TopSitesGridItemTitle">
|
||||||
<item name="android:textColor">@color/top_bookmark_item_title</item>
|
<item name="android:textColor">@color/top_sites_grid_item_title</item>
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
<item name="android:singleLine">true</item>
|
<item name="android:singleLine">true</item>
|
||||||
<item name="android:ellipsize">none</item>
|
<item name="android:ellipsize">none</item>
|
||||||
|
@ -158,6 +158,8 @@
|
||||||
<item name="android:divider">#E7ECF0</item>
|
<item name="android:divider">#E7ECF0</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.TopSitesListView" parent="Widget.BookmarksListView"/>
|
||||||
|
|
||||||
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
|
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
|
||||||
|
|
||||||
<style name="Widget.HomeBanner"/>
|
<style name="Widget.HomeBanner"/>
|
||||||
|
@ -201,6 +203,8 @@
|
||||||
<item name="android:paddingRight">10dip</item>
|
<item name="android:paddingRight">10dip</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator"/>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
TextAppearance
|
TextAppearance
|
||||||
Note: Gecko uses light theme as default, while Android uses dark.
|
Note: Gecko uses light theme as default, while Android uses dark.
|
||||||
|
|
|
@ -78,9 +78,9 @@
|
||||||
<item name="android:textViewStyle">@style/Widget.TextView</item>
|
<item name="android:textViewStyle">@style/Widget.TextView</item>
|
||||||
<item name="android:spinnerStyle">@style/Widget.Spinner</item>
|
<item name="android:spinnerStyle">@style/Widget.Spinner</item>
|
||||||
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
|
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
|
||||||
<item name="bookmarkThumbnailViewStyle">@style/Widget.BookmarkThumbnailView</item>
|
<item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
|
||||||
<item name="topBookmarkItemViewStyle">@style/Widget.TopBookmarkItemView</item>
|
<item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
|
||||||
<item name="topBookmarksViewStyle">@style/Widget.TopBookmarksView</item>
|
<item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
|
||||||
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
|
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -222,9 +222,9 @@
|
||||||
<string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
|
<string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
|
||||||
<string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
|
<string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
|
||||||
<string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
|
<string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
|
||||||
<string name="contextmenu_top_bookmarks_edit">&contextmenu_top_bookmarks_edit;</string>
|
<string name="contextmenu_top_sites_edit">&contextmenu_top_sites_edit;</string>
|
||||||
<string name="contextmenu_top_bookmarks_pin">&contextmenu_top_bookmarks_pin;</string>
|
<string name="contextmenu_top_sites_pin">&contextmenu_top_sites_pin;</string>
|
||||||
<string name="contextmenu_top_bookmarks_unpin">&contextmenu_top_bookmarks_unpin;</string>
|
<string name="contextmenu_top_sites_unpin">&contextmenu_top_sites_unpin;</string>
|
||||||
|
|
||||||
<string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
|
<string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
|
||||||
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
|
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
|
||||||
|
@ -252,6 +252,7 @@
|
||||||
<string name="button_yes">&button_yes;</string>
|
<string name="button_yes">&button_yes;</string>
|
||||||
<string name="button_no">&button_no;</string>
|
<string name="button_no">&button_no;</string>
|
||||||
|
|
||||||
|
<string name="home_top_sites_title">&home_top_sites_title;</string>
|
||||||
<string name="home_history_title">&home_history_title;</string>
|
<string name="home_history_title">&home_history_title;</string>
|
||||||
<string name="home_last_tabs_title">&home_last_tabs_title;</string>
|
<string name="home_last_tabs_title">&home_last_tabs_title;</string>
|
||||||
<string name="home_last_tabs_open">&home_last_tabs_open;</string>
|
<string name="home_last_tabs_open">&home_last_tabs_open;</string>
|
||||||
|
@ -263,7 +264,7 @@
|
||||||
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
|
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
|
||||||
<string name="home_reading_list_hint">&home_reading_list_hint;</string>
|
<string name="home_reading_list_hint">&home_reading_list_hint;</string>
|
||||||
<string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
|
<string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
|
||||||
<string name="pin_bookmark_dialog_hint">&pin_bookmark_dialog_hint;</string>
|
<string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
|
||||||
|
|
||||||
<string name="filepicker_title">&filepicker_title;</string>
|
<string name="filepicker_title">&filepicker_title;</string>
|
||||||
<string name="filepicker_audio_title">&filepicker_audio_title;</string>
|
<string name="filepicker_audio_title">&filepicker_audio_title;</string>
|
||||||
|
|
|
@ -24,13 +24,28 @@ import java.util.ArrayList;
|
||||||
* To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
|
* To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
|
||||||
*/
|
*/
|
||||||
abstract class AboutHomeTest extends BaseTest {
|
abstract class AboutHomeTest extends BaseTest {
|
||||||
protected enum AboutHomeTabs {MOST_VISITED, MOST_RECENT, TABS_FROM_LAST_TIME, BOOKMARKS, READING_LIST};
|
protected enum AboutHomeTabs {HISTORY, MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST};
|
||||||
protected ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
|
private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
|
||||||
add("HISTORY");
|
add("TOP_SITES");
|
||||||
add("BOOKMARKS");
|
add("BOOKMARKS");
|
||||||
add("READING_LIST");
|
add("READING_LIST");
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
if (aboutHomeTabs.size() < 4) {
|
||||||
|
// Update it for tablets vs. phones.
|
||||||
|
if (mDevice.type.equals("phone")) {
|
||||||
|
aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
|
||||||
|
} else {
|
||||||
|
aboutHomeTabs.add(AboutHomeTabs.HISTORY.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME: Write new versions of these methods and update their consumers to use the new about:home pages.
|
* FIXME: Write new versions of these methods and update their consumers to use the new about:home pages.
|
||||||
*/
|
*/
|
||||||
|
@ -85,7 +100,6 @@ abstract class AboutHomeTest extends BaseTest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME: rewrite this to work with fig when rewriting the testBookmarksTab test
|
* FIXME: rewrite this to work with fig when rewriting the testBookmarksTab test
|
||||||
* This method will edit the bookmark with index = bookmarkIndex from the list of bookmarks
|
* This method will edit the bookmark with index = bookmarkIndex from the list of bookmarks
|
||||||
|
@ -142,69 +156,120 @@ abstract class AboutHomeTest extends BaseTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wait in order for the about:home tab to be rendered after drag/tab selection
|
// A wait in order for the about:home tab to be rendered after drag/tab selection
|
||||||
private void waitForAboutHomeTab(final int tabIndex) {
|
private void waitForAboutHomeTab(final int tabIndex) {
|
||||||
boolean correctTab = waitForCondition(new Condition() {
|
boolean correctTab = waitForCondition(new Condition() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isSatisfied() {
|
public boolean isSatisfied() {
|
||||||
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
|
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
|
||||||
return (pager.getCurrentItem() == tabIndex);
|
return (pager.getCurrentItem() == tabIndex);
|
||||||
}
|
}
|
||||||
}, MAX_WAIT_MS);
|
}, MAX_WAIT_MS);
|
||||||
mAsserter.ok(correctTab, "Checking that the correct tab is displayed", "The " + aboutHomeTabs.get(tabIndex) + " tab is displayed");
|
mAsserter.ok(correctTab, "Checking that the correct tab is displayed", "The " + aboutHomeTabs.get(tabIndex) + " tab is displayed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clickAboutHomeTab(AboutHomeTabs tab) {
|
||||||
|
mSolo.clickOnText(tab.toString().replace("_", " "));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method can be used to open the different tabs of about:home
|
* Swipes to an about:home tab.
|
||||||
* @param AboutHomeTabs enum item {MOST_VISITED, MOST_RECENT, TABS_FROM_LAST_TIME, BOOKMARKS, READING_LIST}
|
* @param int swipeVector Value and direction to swipe (go left for negative, right for positive).
|
||||||
|
*/
|
||||||
|
private void swipeAboutHome(int swipeVector) {
|
||||||
|
// Increase swipe width, which will especially impact tablets.
|
||||||
|
int swipeWidth = mDriver.getGeckoWidth() - 1;
|
||||||
|
int swipeHeight = mDriver.getGeckoHeight() / 2;
|
||||||
|
|
||||||
|
if (swipeVector >= 0) {
|
||||||
|
// Emulate swipe motion from right to left.
|
||||||
|
for (int i = 0; i < swipeVector; i++) {
|
||||||
|
mActions.drag(swipeWidth, 0, swipeHeight, swipeHeight);
|
||||||
|
mSolo.sleep(100);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Emulate swipe motion from left to right.
|
||||||
|
for (int i = 0; i > swipeVector; i--) {
|
||||||
|
mActions.drag(0, swipeWidth, swipeHeight, swipeHeight);
|
||||||
|
mSolo.sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method can be used to open the different tabs of about:home.
|
||||||
|
*
|
||||||
|
* @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
|
||||||
*/
|
*/
|
||||||
protected void openAboutHomeTab(AboutHomeTabs tab) {
|
protected void openAboutHomeTab(AboutHomeTabs tab) {
|
||||||
int halfWidth = mDriver.getGeckoWidth() / 2;
|
|
||||||
int halfHeight = mDriver.getGeckoHeight() / 2;
|
|
||||||
focusUrlBar();
|
focusUrlBar();
|
||||||
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
|
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
|
||||||
|
|
||||||
|
// Handle tablets by just clicking the visible tab title.
|
||||||
|
if (mDevice.type.equals("tablet")) {
|
||||||
|
// Just click for tablets, since all the titles are visible.
|
||||||
|
if (AboutHomeTabs.MOST_RECENT == tab || AboutHomeTabs.TABS_FROM_LAST_TIME == tab) {
|
||||||
|
mSolo.clickOnText(AboutHomeTabs.HISTORY.toString());
|
||||||
|
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
||||||
|
|
||||||
|
switch (tab) {
|
||||||
|
case MOST_RECENT: {
|
||||||
|
mSolo.clickOnView(tabwidget.getChildAt(0));
|
||||||
|
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TABS_FROM_LAST_TIME: {
|
||||||
|
mSolo.clickOnView(tabwidget.getChildAt(1));
|
||||||
|
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clickAboutHomeTab(tab);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle phones (non-tablets).
|
||||||
|
final int currentTabIndex = pager.getCurrentItem();
|
||||||
|
int tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
|
case TOP_SITES : {
|
||||||
|
swipeAboutHome(tabOffset);
|
||||||
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case BOOKMARKS : {
|
case BOOKMARKS : {
|
||||||
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
|
swipeAboutHome(tabOffset);
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MOST_RECENT: {
|
case MOST_RECENT: {
|
||||||
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
|
// MOST_RECENT is contained in the HISTORY tab.
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
|
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
|
||||||
mActions.drag(0, halfWidth, halfHeight, halfHeight);
|
swipeAboutHome(tabOffset);
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(1));
|
|
||||||
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case READING_LIST: {
|
|
||||||
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
|
|
||||||
mActions.drag(halfWidth, 0, halfHeight, halfHeight);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MOST_VISITED: {
|
|
||||||
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
|
|
||||||
mActions.drag(0, halfWidth, halfHeight, halfHeight);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(0));
|
mSolo.clickOnView(tabwidget.getChildAt(0));
|
||||||
|
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TABS_FROM_LAST_TIME: {
|
case TABS_FROM_LAST_TIME: {
|
||||||
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
|
// TABS_FROM_LAST_TIME is contained in the HISTORY tab.
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
|
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
|
||||||
mActions.drag(0, halfWidth, halfHeight, halfHeight);
|
swipeAboutHome(tabOffset);
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(2));
|
mSolo.clickOnView(tabwidget.getChildAt(1));
|
||||||
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
|
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case READING_LIST: {
|
||||||
|
swipeAboutHome(tabOffset);
|
||||||
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -616,6 +616,7 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
|
||||||
public String type; // "tablet" or "phone"
|
public String type; // "tablet" or "phone"
|
||||||
public final int width;
|
public final int width;
|
||||||
public final int height;
|
public final int height;
|
||||||
|
public final float density;
|
||||||
|
|
||||||
public Device() {
|
public Device() {
|
||||||
// Determine device version
|
// Determine device version
|
||||||
|
@ -634,6 +635,7 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
|
||||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||||
height = dm.heightPixels;
|
height = dm.heightPixels;
|
||||||
width = dm.widthPixels;
|
width = dm.widthPixels;
|
||||||
|
density = dm.density;
|
||||||
// Determine device type
|
// Determine device type
|
||||||
type = "phone";
|
type = "phone";
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -90,6 +90,7 @@ class StringHelper {
|
||||||
|
|
||||||
// Labels for the about:home tabs
|
// Labels for the about:home tabs
|
||||||
public static final String HISTORY_LABEL = "HISTORY";
|
public static final String HISTORY_LABEL = "HISTORY";
|
||||||
|
public static final String TOP_SITES_LABEL = "TOP SITES";
|
||||||
public static final String BOOKMARKS_LABEL = "BOOKMARKS";
|
public static final String BOOKMARKS_LABEL = "BOOKMARKS";
|
||||||
public static final String READING_LIST_LABEL = "READING LIST";
|
public static final String READING_LIST_LABEL = "READING LIST";
|
||||||
public static final String MOST_RECENT_LABEL = "Most recent";
|
public static final String MOST_RECENT_LABEL = "Most recent";
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Browser Blank Page 04</title>
|
||||||
|
<body>
|
||||||
|
<p>Browser Blank Page 04</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Browser Blank Page 05</title>
|
||||||
|
<body>
|
||||||
|
<p>Browser Blank Page 05</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -35,6 +35,9 @@ public class testShareLink extends AboutHomeTest {
|
||||||
ArrayList<String> shareOptions;
|
ArrayList<String> shareOptions;
|
||||||
blockForGeckoReady();
|
blockForGeckoReady();
|
||||||
|
|
||||||
|
// FIXME: This is a temporary hack workaround for a permissions problem.
|
||||||
|
openAboutHomeTab(AboutHomeTabs.READING_LIST);
|
||||||
|
|
||||||
inputAndLoadUrl(url);
|
inputAndLoadUrl(url);
|
||||||
verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
|
verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
|
||||||
|
|
||||||
|
@ -59,12 +62,8 @@ public class testShareLink extends AboutHomeTest {
|
||||||
mSolo.clickLongOnText(urlTitle);
|
mSolo.clickLongOnText(urlTitle);
|
||||||
verifySharePopup(shareOptions,"urlbar");
|
verifySharePopup(shareOptions,"urlbar");
|
||||||
|
|
||||||
// Test link Context Menu
|
|
||||||
DisplayMetrics dm = new DisplayMetrics();
|
|
||||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
|
||||||
|
|
||||||
// The link has a 60px height, so let's try to hit the middle
|
// The link has a 60px height, so let's try to hit the middle
|
||||||
float top = mDriver.getGeckoTop() + 30 * dm.density;
|
float top = mDriver.getGeckoTop() + 30 * mDevice.density;
|
||||||
float left = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() / 2;
|
float left = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() / 2;
|
||||||
mSolo.clickLongOnScreen(left, top);
|
mSolo.clickLongOnScreen(left, top);
|
||||||
verifySharePopup("Share Link",shareOptions,"Link");
|
verifySharePopup("Share Link",shareOptions,"Link");
|
||||||
|
@ -75,26 +74,37 @@ public class testShareLink extends AboutHomeTest {
|
||||||
ListView bookmarksList = findListViewWithTag("bookmarks");
|
ListView bookmarksList = findListViewWithTag("bookmarks");
|
||||||
mAsserter.is(waitForListToLoad(bookmarksList), true, "list is properly loaded");
|
mAsserter.is(waitForListToLoad(bookmarksList), true, "list is properly loaded");
|
||||||
|
|
||||||
int width = mDriver.getGeckoWidth();
|
|
||||||
int height = mDriver.getGeckoHeight();
|
|
||||||
|
|
||||||
// Scroll down a bit so that the bookmarks list has more
|
|
||||||
// items on screen.
|
|
||||||
mActions.drag(width / 2, width / 2, height - 10, height / 2);
|
|
||||||
|
|
||||||
View bookmarksItem = bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount());
|
View bookmarksItem = bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount());
|
||||||
mSolo.clickLongOnView(bookmarksItem);
|
mSolo.clickLongOnView(bookmarksItem);
|
||||||
verifySharePopup(shareOptions,"bookmarks");
|
verifySharePopup(shareOptions,"bookmarks");
|
||||||
|
|
||||||
// Test the share popup in the Most Visited tab
|
// Prepopulate top sites with history items to overflow tiles.
|
||||||
openAboutHomeTab(AboutHomeTabs.MOST_VISITED);
|
// We are trying to move away from using reflection and doing more black-box testing.
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_01.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_02.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_03.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_04.html"));
|
||||||
|
if (mDevice.type.equals("tablet")) {
|
||||||
|
// Tablets have more tile spaces to fill.
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_05.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_boxes.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_search.html"));
|
||||||
|
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_text_page.html"));
|
||||||
|
}
|
||||||
|
|
||||||
ListView mostVisitedList = findListViewWithTag("most_visited");
|
// Test the share popup in Top Sites.
|
||||||
mAsserter.is(waitForListToLoad(mostVisitedList), true, "list is properly loaded");
|
openAboutHomeTab(AboutHomeTabs.TOP_SITES);
|
||||||
|
|
||||||
View mostVisitedItem = mostVisitedList.getChildAt(mostVisitedList.getHeaderViewsCount());
|
// Scroll down a bit so that the top sites list has more items on screen.
|
||||||
|
int width = mDriver.getGeckoWidth();
|
||||||
|
int height = mDriver.getGeckoHeight();
|
||||||
|
mActions.drag(width / 2, width / 2, height - 10, height / 2);
|
||||||
|
|
||||||
|
ListView topSitesList = findListViewWithTag("top_sites");
|
||||||
|
mAsserter.is(waitForListToLoad(topSitesList), true, "list is properly loaded");
|
||||||
|
View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
|
||||||
mSolo.clickLongOnView(mostVisitedItem);
|
mSolo.clickLongOnView(mostVisitedItem);
|
||||||
verifySharePopup(shareOptions,"most visited");
|
verifySharePopup(shareOptions,"top_sites");
|
||||||
|
|
||||||
// Test the share popup in the Most Recent tab
|
// Test the share popup in the Most Recent tab
|
||||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
||||||
|
|
|
@ -207,9 +207,10 @@
|
||||||
</toolbox>
|
</toolbox>
|
||||||
|
|
||||||
<vbox id="appcontent" flex="1">
|
<vbox id="appcontent" flex="1">
|
||||||
<findbar id="FindToolbar" browserid="content" position="top"/>
|
|
||||||
<browser id="content" type="content-primary" name="content" src="about:blank" flex="1"
|
<browser id="content" type="content-primary" name="content" src="about:blank" flex="1"
|
||||||
context="viewSourceContextMenu" showcaret="true" tooltip="aHTMLTooltip"/>
|
context="viewSourceContextMenu" showcaret="true" tooltip="aHTMLTooltip"/>
|
||||||
|
<findbar id="FindToolbar" browserid="content"/>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<statusbar id="status-bar" class="chromeclass-status">
|
<statusbar id="status-bar" class="chromeclass-status">
|
||||||
|
|
|
@ -62,7 +62,6 @@ toolkit.jar:
|
||||||
content/global/bindings/expander.xml (widgets/expander.xml)
|
content/global/bindings/expander.xml (widgets/expander.xml)
|
||||||
* content/global/bindings/filefield.xml (widgets/filefield.xml)
|
* content/global/bindings/filefield.xml (widgets/filefield.xml)
|
||||||
*+ content/global/bindings/findbar.xml (widgets/findbar.xml)
|
*+ content/global/bindings/findbar.xml (widgets/findbar.xml)
|
||||||
content/global/bindings/findbar.css (widgets/findbar.css)
|
|
||||||
content/global/bindings/general.xml (widgets/general.xml)
|
content/global/bindings/general.xml (widgets/general.xml)
|
||||||
content/global/bindings/groupbox.xml (widgets/groupbox.xml)
|
content/global/bindings/groupbox.xml (widgets/groupbox.xml)
|
||||||
*+ content/global/bindings/listbox.xml (widgets/listbox.xml)
|
*+ content/global/bindings/listbox.xml (widgets/listbox.xml)
|
||||||
|
|
|
@ -1,41 +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/. */
|
|
||||||
|
|
||||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
|
||||||
|
|
||||||
findbar {
|
|
||||||
transition-property: transform, opacity, visibility;
|
|
||||||
transition-duration: 120ms, 120ms, 0s;
|
|
||||||
transition-timing-function: ease-in-out, ease-in-out, linear;
|
|
||||||
|
|
||||||
/* The following positioning properties only take an effect during findbar
|
|
||||||
* transitions. The findbar binding sets position:absolute during that time
|
|
||||||
* on the findbar.
|
|
||||||
*/
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
findbar[position="top"] {
|
|
||||||
top: 0;
|
|
||||||
bottom: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
findbar > hbox {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
findbar[hidden] {
|
|
||||||
/* Override display:none to make the transition work. */
|
|
||||||
display: -moz-box;
|
|
||||||
visibility: collapse;
|
|
||||||
opacity: 0;
|
|
||||||
transition-delay: 0s, 0s, 120ms;
|
|
||||||
transform: translateY(2em);
|
|
||||||
}
|
|
||||||
|
|
||||||
findbar[position="top"][hidden] {
|
|
||||||
transform: translateY(-2em);
|
|
||||||
}
|
|
|
@ -144,7 +144,6 @@
|
||||||
<binding id="findbar"
|
<binding id="findbar"
|
||||||
extends="chrome://global/content/bindings/toolbar.xml#toolbar">
|
extends="chrome://global/content/bindings/toolbar.xml#toolbar">
|
||||||
<resources>
|
<resources>
|
||||||
<stylesheet src="chrome://global/content/bindings/findbar.css"/>
|
|
||||||
<stylesheet src="chrome://global/skin/findBar.css"/>
|
<stylesheet src="chrome://global/skin/findBar.css"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
@ -208,8 +207,6 @@
|
||||||
<field name="_flashFindBar">0</field>
|
<field name="_flashFindBar">0</field>
|
||||||
<field name="_initialFlashFindBarCount">6</field>
|
<field name="_initialFlashFindBarCount">6</field>
|
||||||
|
|
||||||
<field name="_contentScrollOffset">0</field>
|
|
||||||
|
|
||||||
<property name="prefillWithSelection"
|
<property name="prefillWithSelection"
|
||||||
onget="return this.getAttribute('prefillwithselection') != 'false'"
|
onget="return this.getAttribute('prefillwithselection') != 'false'"
|
||||||
onset="this.setAttribute('prefillwithselection', val); return val;"/>
|
onset="this.setAttribute('prefillwithselection', val); return val;"/>
|
||||||
|
@ -546,30 +543,8 @@
|
||||||
|
|
||||||
this._updateFindUI();
|
this._updateFindUI();
|
||||||
if (this.hidden) {
|
if (this.hidden) {
|
||||||
// Use position:absolute during the transition.
|
|
||||||
this.style.position = "absolute";
|
|
||||||
this.parentNode.style.position = "relative";
|
|
||||||
|
|
||||||
// Apparently a flush is necessary after setting position:relative
|
|
||||||
// on our parentNode, otherwise setting hidden to false won't
|
|
||||||
// animate the transform change.
|
|
||||||
this.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
|
|
||||||
// Set a height on the findbar that's at least as much as the
|
|
||||||
// current height, but guaranteed to be an integer number of
|
|
||||||
// screen pixels.
|
|
||||||
// This way, reapplying position:static on the findbar after the
|
|
||||||
// fade in animation won't cause the browser contents to wiggle.
|
|
||||||
let [chromeOffset, contentScrollOffset] = this._findOffsets();
|
|
||||||
this.style.height = chromeOffset + "px";
|
|
||||||
this._contentScrollOffset = contentScrollOffset;
|
|
||||||
|
|
||||||
// Wait for the findbar appearance animation to end before
|
|
||||||
// changing the browser size.
|
|
||||||
this.addEventListener("transitionend", this);
|
|
||||||
|
|
||||||
this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
|
this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
|
||||||
|
|
||||||
let event = document.createEvent("Events");
|
let event = document.createEvent("Events");
|
||||||
|
@ -596,15 +571,6 @@
|
||||||
this.browser.finder.removeSelection();
|
this.browser.finder.removeSelection();
|
||||||
this._findField.blur();
|
this._findField.blur();
|
||||||
|
|
||||||
this.addEventListener("transitionend", this);
|
|
||||||
|
|
||||||
// Revert browser scroll shift + findbar static positioning.
|
|
||||||
if (this.getAttribute("position") == "top" &&
|
|
||||||
this.style.position != "absolute") {
|
|
||||||
this._browser.contentWindow.scrollBy(0, -this._contentScrollOffset);
|
|
||||||
}
|
|
||||||
this.style.position = "absolute";
|
|
||||||
|
|
||||||
this._cancelTimers();
|
this._cancelTimers();
|
||||||
|
|
||||||
this._findFailedString = null;
|
this._findFailedString = null;
|
||||||
|
@ -828,58 +794,10 @@
|
||||||
case "keypress":
|
case "keypress":
|
||||||
this._onBrowserKeypress(aEvent);
|
this._onBrowserKeypress(aEvent);
|
||||||
break;
|
break;
|
||||||
case "transitionend":
|
|
||||||
if (aEvent.target == this &&
|
|
||||||
aEvent.propertyName == "transform") {
|
|
||||||
this.removeEventListener("transitionend", this);
|
|
||||||
|
|
||||||
// Change the browser size in such a way that the region that's
|
|
||||||
// overlapped by the findbar can be scrolled to, but try to
|
|
||||||
// avoid a visual shift of the browser contents.
|
|
||||||
this.style.removeProperty("position");
|
|
||||||
if (this.getAttribute("position") == "top" &&
|
|
||||||
!this.hidden) {
|
|
||||||
this._browser.contentWindow.scrollBy(0, this._contentScrollOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'd like to remove position:relative from this.parentNode,
|
|
||||||
// but that unfortunately causes unnecessary repainting.
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="_screenPixelsPerCSSPixel">
|
|
||||||
<parameter name="aWindow"/>
|
|
||||||
<body><![CDATA[
|
|
||||||
return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Components.interfaces.nsIDOMWindowUtils)
|
|
||||||
.screenPixelsPerCSSPixel;
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
- Find two numbers, one in chrome CSS pixels and one in integer
|
|
||||||
- content CSS pixels, that are about the same as (but not less than)
|
|
||||||
- the height of the findbar. These two numbers hopefully map to the
|
|
||||||
- same number of integer screen pixels.
|
|
||||||
- We want to avoid shifting of the page even when chrome and content
|
|
||||||
- have different zoom factors, and scrollBy() only accepts integers.
|
|
||||||
-->
|
|
||||||
<method name="_findOffsets">
|
|
||||||
<body><![CDATA[
|
|
||||||
let chromeFactor = this._screenPixelsPerCSSPixel(window);
|
|
||||||
let contentFactor = this._screenPixelsPerCSSPixel(this._browser.contentWindow);
|
|
||||||
|
|
||||||
let findbarHeightScreen = this.getBoundingClientRect().height * chromeFactor;
|
|
||||||
let contentScrollOffset = Math.ceil(findbarHeightScreen / contentFactor);
|
|
||||||
let estimatedScrollOffsetInScreenPixels = Math.round(contentScrollOffset * contentFactor);
|
|
||||||
let chromeOffset = estimatedScrollOffsetInScreenPixels / chromeFactor;
|
|
||||||
return [chromeOffset, contentScrollOffset];
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="_enableFindButtons">
|
<method name="_enableFindButtons">
|
||||||
<parameter name="aEnable"/>
|
<parameter name="aEnable"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
|
|
|
@ -420,7 +420,11 @@ let NetworkHelper = {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/^application\/[a-z-]+\+xml$/.test(aMimeType)) {
|
// XML and JSON often come with custom MIME types, so in addition to the
|
||||||
|
// standard "application/xml" and "application/json", we also look for
|
||||||
|
// variants like "application/x-bigcorp-xml" by checking for either string
|
||||||
|
// after any word boundary.
|
||||||
|
if (/^application\/[a-z-]+\b(xml|json)/.test(aMimeType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,18 @@ findbar {
|
||||||
border-top: 2px solid;
|
border-top: 2px solid;
|
||||||
-moz-border-top-colors: ThreeDShadow ThreeDHighlight;
|
-moz-border-top-colors: ThreeDShadow ThreeDHighlight;
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
|
transition-property: margin-bottom, opacity, visibility;
|
||||||
|
transition-duration: 150ms, 150ms, 0s;
|
||||||
|
transition-timing-function: ease-in-out, ease-in-out, linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
findbar[position="top"] {
|
findbar[hidden] {
|
||||||
border-top-style: none;
|
/* Override display:none to make the transition work. */
|
||||||
border-bottom: 1px solid ThreeDShadow;
|
display: -moz-box;
|
||||||
|
visibility: collapse;
|
||||||
|
margin-bottom: -1em;
|
||||||
|
opacity: 0;
|
||||||
|
transition-delay: 0s, 0s, 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.findbar-closebutton {
|
.findbar-closebutton {
|
||||||
|
|
|
@ -10,11 +10,18 @@ findbar {
|
||||||
border-top: @scopeBarSeparatorBorder@;
|
border-top: @scopeBarSeparatorBorder@;
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
padding: 4px 2px;
|
padding: 4px 2px;
|
||||||
|
transition-property: margin-bottom, opacity, visibility;
|
||||||
|
transition-duration: 150ms, 150ms, 0s;
|
||||||
|
transition-timing-function: ease-in-out, ease-in-out, linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
findbar[position="top"] {
|
findbar[hidden] {
|
||||||
border-top: none;
|
/* Override display:none to make the transition work. */
|
||||||
border-bottom: @scopeBarSeparatorBorder@;
|
display: -moz-box;
|
||||||
|
visibility: collapse;
|
||||||
|
margin-bottom: -1em;
|
||||||
|
opacity: 0;
|
||||||
|
transition-delay: 0s, 0s, 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
findbar:-moz-lwtheme {
|
findbar:-moz-lwtheme {
|
||||||
|
|
|
@ -11,11 +11,18 @@ findbar {
|
||||||
background-size: 100% 2px;
|
background-size: 100% 2px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
|
transition-property: margin-bottom, opacity, visibility;
|
||||||
|
transition-duration: 150ms, 150ms, 0s;
|
||||||
|
transition-timing-function: ease-in-out, ease-in-out, linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
findbar[position="top"] {
|
findbar[hidden] {
|
||||||
background-image: none;
|
/* Override display:none to make the transition work. */
|
||||||
box-shadow: 0 -1px 0 rgba(0,0,0,.1) inset;
|
display: -moz-box;
|
||||||
|
visibility: collapse;
|
||||||
|
margin-bottom: -1em;
|
||||||
|
opacity: 0;
|
||||||
|
transition-delay: 0s, 0s, 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.findbar-closebutton {
|
.findbar-closebutton {
|
||||||
|
|