Merge fx-team to m-c a=merge

This commit is contained in:
Wes Kocher 2014-06-30 18:22:10 -07:00
commit 7ccae990fa
69 changed files with 48872 additions and 214 deletions

View File

@ -27,6 +27,7 @@ XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
return devtools.require('devtools/server/actors/memory').MemoryFront;
});
Cu.import('resource://gre/modules/AppFrames.jsm');
/**
* The Developer HUD is an on-device developer tool that displays widgets,
@ -36,7 +37,6 @@ XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
let developerHUD = {
_targets: new Map(),
_frames: new Map(),
_client: null,
_conn: null,
_watchers: [],
@ -77,15 +77,9 @@ let developerHUD = {
}
}
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'message-manager-disconnect', false);
AppFrames.addObserver(this);
let systemapp = document.querySelector('#systemapp');
this.trackFrame(systemapp);
let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
for (let frame of frames) {
for (let frame of AppFrames.list()) {
this.trackFrame(frame);
}
@ -102,9 +96,7 @@ let developerHUD = {
this.untrackFrame(frame);
}
Services.obs.removeObserver(this, 'remote-browser-shown');
Services.obs.removeObserver(this, 'inprocess-browser-shown');
Services.obs.removeObserver(this, 'message-manager-disconnect');
AppFrames.removeObserver(this);
this._client.close();
delete this._client;
@ -140,41 +132,12 @@ let developerHUD = {
}
},
observe: function dwp_observe(subject, topic, data) {
if (!this._client)
return;
onAppFrameCreated: function (frame, isFirstAppFrame) {
this.trackFrame(frame);
},
let frame;
switch(topic) {
// listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
case 'remote-browser-shown':
case 'inprocess-browser-shown':
let frameLoader = subject;
// get a ref to the app <iframe>
frameLoader.QueryInterface(Ci.nsIFrameLoader);
// Ignore notifications that aren't from a BrowserOrApp
if (!frameLoader.ownerIsBrowserOrAppFrame) {
return;
}
frame = frameLoader.ownerElement;
if (!frame.appManifestURL) // Ignore all frames but app frames
return;
this.trackFrame(frame);
this._frames.set(frameLoader.messageManager, frame);
break;
// Every time an iframe is destroyed, its message manager also is
case 'message-manager-disconnect':
let mm = subject;
frame = this._frames.get(mm);
if (!frame)
return;
this.untrackFrame(frame);
this._frames.delete(mm);
break;
}
onAppFrameDestroyed: function (frame, isLastAppFrame) {
this.untrackFrame(frame);
},
log: function dwp_log(message) {

View File

@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
this.EXPORTED_SYMBOLS = ['AppFrames'];
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/SystemAppProxy.jsm');
const listeners = [];
const Observer = {
// Save a map of (MessageManager => Frame) to be able to dispatch
// the FrameDestroyed event with a frame reference.
_frames: new Map(),
// Also save current number of iframes opened by app
_apps: new Map(),
start: function () {
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'message-manager-disconnect', false);
SystemAppProxy.getAppFrames().forEach((frame) => {
let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
this._frames.set(mm, frame);
let mozapp = frame.getAttribute('mozapp');
this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
});
},
stop: function () {
Services.obs.removeObserver(this, 'remote-browser-shown');
Services.obs.removeObserver(this, 'inprocess-browser-shown');
Services.obs.removeObserver(this, 'message-manager-disconnect');
this._frames.clear();
this._apps.clear();
},
observe: function (subject, topic, data) {
switch(topic) {
// Listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
case 'remote-browser-shown':
case 'inprocess-browser-shown':
let frameLoader = subject;
// get a ref to the app <iframe>
frameLoader.QueryInterface(Ci.nsIFrameLoader);
let frame = frameLoader.ownerElement;
let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
this.onMessageManagerCreated(mm, frame);
break;
// Every time an iframe is destroyed, its message manager also is
case 'message-manager-disconnect':
this.onMessageManagerDestroyed(subject);
break;
}
},
onMessageManagerCreated: function (mm, frame) {
this._frames.set(mm, frame);
let mozapp = frame.getAttribute('mozapp');
let count = (this._apps.get(mozapp) || 0) + 1;
this._apps.set(mozapp, count);
let isFirstAppFrame = (count === 1);
listeners.forEach(function (listener) {
try {
listener.onAppFrameCreated(frame, isFirstAppFrame);
} catch(e) {
dump('Exception while calling Frames.jsm listener:' + e + '\n' + e.stack + '\n');
}
});
},
onMessageManagerDestroyed: function (mm) {
let frame = this._frames.get(mm);
if (!frame) {
// We receive an event for a non mozapp message manager
return;
}
this._frames.delete(mm);
let mozapp = frame.getAttribute('mozapp');
let count = (this._apps.get(mozapp) || 0) - 1;
this._apps.set(mozapp, count);
let isLastAppFrame = (count === 0);
listeners.forEach(function (listener) {
try {
listener.onAppFrameDestroyed(frame, isLastAppFrame);
} catch(e) {
dump('Exception while calling Frames.jsm listener:' + e + '\n' + e.stack + '\n');
}
});
}
};
const AppFrames = this.AppFrames = {
list: () => SystemAppProxy.getAppFrames(),
addObserver: function (listener) {
if (listeners.indexOf(listener) !== -1) {
return;
}
listeners.push(listener);
if (listeners.length == 1) {
Observer.start();
}
},
removeObserver: function (listener) {
let idx = listeners.indexOf(listener);
if (idx !== -1) {
listeners.splice(idx, 1);
}
if (listeners.length === 0) {
Observer.stop();
}
}
};

View File

@ -115,8 +115,27 @@ let SystemAppProxy = {
this._pendingListeners.splice(idx, 1);
}
}
}
},
getAppFrames: function systemApp_getAppFrames() {
let systemAppFrame = this._frame;
if (!systemAppFrame) {
return [];
}
let list = [systemAppFrame];
// List all app frames hosted in the system app: the homescreen,
// all regular apps, activities, rocket bar, attention screen and the keyboard.
// Bookmark apps and other system app internal frames like captive portal
// are also hosted in system app, but they are not using mozapp attribute.
let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
for (let i = 0; i < frames.length; i++) {
list.push(frames[i]);
}
return list;
}
};
this.SystemAppProxy = SystemAppProxy;

View File

@ -45,6 +45,7 @@ if CONFIG['MOZ_UPDATER']:
EXTRA_JS_MODULES += [
'AlertsHelper.jsm',
'AppFrames.jsm',
'ContentRequestHelper.jsm',
'ErrorPage.jsm',
'SignInToWebsite.jsm',

View File

@ -42,7 +42,7 @@ const EXPECTED_REFLOWS = [
// SessionStore.getWindowDimensions()
"ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
"@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",

View File

@ -36,6 +36,7 @@ support-files =
[browser_inspector_highlighter.js]
[browser_inspector_iframeTest.js]
[browser_inspector_infobar.js]
skip-if = true # Bug 1028609
[browser_inspector_initialization.js]
[browser_inspector_invalidate.js]
[browser_inspector_menu.js]

View File

@ -348,7 +348,7 @@ CssHtmlTree.prototype = {
type = overlays.VIEW_NODE_VALUE_TYPE;
} else if (classes.contains("theme-link")) {
type = overlays.VIEW_NODE_IMAGE_URL_TYPE;
value.url = node.textContent;
value.url = node.href;
} else {
return null;
}

View File

@ -1236,7 +1236,7 @@ CssRuleView.prototype = {
value = {
property: getPropertyNameAndValue(node).name,
value: node.parentNode.textContent,
url: node.textContent,
url: node.href,
enabled: prop.enabled,
overridden: prop.overridden,
pseudoElement: prop.rule.pseudoElement,

View File

@ -290,10 +290,6 @@ TooltipsOverlay.prototype = {
// Image preview tooltip
if (type === VIEW_NODE_IMAGE_URL_TYPE && inspector.hasUrlToImageDataResolver) {
let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
let uri = CssLogic.getBackgroundImageUriFromProperty(prop.value,
prop.sheetHref); // sheetHref is undefined for computed-view properties,
// but we don't care as URIs are absolute
tooltipType = TOOLTIP_IMAGE_TYPE;
}
@ -337,10 +333,8 @@ TooltipsOverlay.prototype = {
if (type === TOOLTIP_IMAGE_TYPE) {
let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
let uri = CssLogic.getBackgroundImageUriFromProperty(nodeInfo.value.value,
nodeInfo.value.sheetHref); // sheetHref is undefined for computed-view
// properties, but we don't care as uris are
// absolute
// nodeInfo contains an absolute uri
let uri = nodeInfo.value.url;
return this.previewTooltip.setRelativeImageContent(uri,
inspector.inspector, dim);
}

View File

@ -100,6 +100,7 @@ skip-if = os == "win" && debug # bug 963492
[browser_styleinspector_tooltip-background-image.js]
[browser_styleinspector_tooltip-closes-on-new-selection.js]
[browser_styleinspector_tooltip-longhand-fontfamily.js]
[browser_styleinspector_tooltip-multiple-background-images.js]
[browser_styleinspector_tooltip-shorthand-fontfamily.js]
[browser_styleinspector_tooltip-size.js]
[browser_styleinspector_transform-highlighter-01.js]

View File

@ -0,0 +1,71 @@
/* 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/ */
"use strict";
// Test for bug 1026921: Ensure the URL of hovered url() node is used instead
// of the first found from the declaration as there might be multiple urls.
let YELLOW_DOT = "data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACX" +
"BIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwCr0o5ngAAABl0RVh0Q29tbWVudABDcmVh" +
"dGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY/j/n6EeAAd9An7Z55GEAAAAAElFTkSuQmCC";
let BLUE_DOT = "data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACX" +
"BIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwlCkCM9QAAABl0RVh0Q29tbWVudABDcmVh" +
"dGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY2Bg+F8PAAKCAX/tPkrkAAAAAElFTkSuQmCC";
let test = asyncTest(function* () {
let TEST_STYLE = "h1 {background: url(" + YELLOW_DOT + "), url(" + BLUE_DOT + ");}";
let PAGE_CONTENT = "<style>" + TEST_STYLE + "</style>" +
"<h1>browser_styleinspector_tooltip-multiple-background-images.js</h1>";
yield addTab("data:text/html,background image tooltip test");
content.document.body.innerHTML = PAGE_CONTENT;
yield testRuleViewUrls();
yield testComputedViewUrls();
});
function* testRuleViewUrls() {
info("Testing tooltips in the rule view");
let { view, inspector } = yield openRuleView();
yield selectNode("h1", inspector);
let {valueSpan} = getRuleViewProperty(view, "h1", "background");
yield performChecks(view, valueSpan);
}
function* testComputedViewUrls() {
info("Testing tooltips in the computed view");
let {view} = yield openComputedView();
let {valueSpan} = getComputedViewProperty(view, "background-image");
yield performChecks(view, valueSpan);
}
/**
* A helper that checks url() tooltips contain correct images
*/
function* performChecks(view, propertyValue) {
function checkTooltip(panel, imageSrc) {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
is(images[0].getAttribute("src"), imageSrc, "The image URL is correct");
}
let links = propertyValue.querySelectorAll(".theme-link");
let panel = view.tooltips.previewTooltip.panel;
info("Checking first link tooltip");
yield assertHoverTooltipOn(view.tooltips.previewTooltip, links[0]);
checkTooltip(panel, YELLOW_DOT);
info("Checking second link tooltip");
yield assertHoverTooltipOn(view.tooltips.previewTooltip, links[1]);
checkTooltip(panel, BLUE_DOT);
}

View File

@ -8,6 +8,7 @@ support-files =
doc_buffer-and-array.html
doc_media-node-creation.html
doc_destroy-nodes.html
doc_connect-toggle.html
440hz_sine.ogg
head.js
@ -30,6 +31,7 @@ support-files =
[browser_wa_graph-click.js]
[browser_wa_graph-render-01.js]
[browser_wa_graph-render-02.js]
[browser_wa_graph-render-03.js]
[browser_wa_graph-markers.js]
[browser_wa_graph-selected.js]
[browser_wa_graph-zoom.js]

View File

@ -30,21 +30,21 @@ function spawnTest() {
yield clickGraphNode(panelWin, nodeIds[1], true);
ok(WebAudioInspectorView.isVisible(), "InspectorView visible after selecting a node.");
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[1], "InspectorView has correct node set.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set.");
yield clickGraphNode(panelWin, nodeIds[2]);
ok(WebAudioInspectorView.isVisible(), "InspectorView still visible after selecting another node.");
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[2], "InspectorView has correct node set on second node.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[2], "InspectorView has correct node set on second node.");
yield clickGraphNode(panelWin, nodeIds[2]);
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[2], "Clicking the same node again works (idempotent).");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[2], "Clicking the same node again works (idempotent).");
yield clickGraphNode(panelWin, $("rect", findGraphNode(panelWin, nodeIds[3])));
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[3], "Clicking on a <rect> works as expected.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[3], "Clicking on a <rect> works as expected.");
yield clickGraphNode(panelWin, $("tspan", findGraphNode(panelWin, nodeIds[4])));
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[4], "Clicking on a <tspan> works as expected.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[4], "Clicking on a <tspan> works as expected.");
ok(WebAudioInspectorView.isVisible(),
"InspectorView still visible after several nodes have been clicked.");

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests to ensure that selected nodes stay selected on graph redraw.
*/
function spawnTest() {
let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_TOGGLE_URL);
let { panelWin } = panel;
let { gFront, $, $$, EVENTS } = panelWin;
reload(target);
let [actors] = yield Promise.all([
getN(gFront, "create-node", 3),
waitForGraphRendered(panelWin, 3, 2)
]);
let nodeIDs = actors.map(actor => actor.actorID);
yield clickGraphNode(panelWin, nodeIDs[1]);
ok(findGraphNode(panelWin, nodeIDs[1]).classList.contains("selected"),
"Node selected once.");
yield once(panelWin, EVENTS.UI_GRAPH_RENDERED);
ok(findGraphNode(panelWin, nodeIDs[1]).classList.contains("selected"),
"Node still selected after rerender.");
yield teardown(panel);
finish();
}

View File

@ -21,7 +21,7 @@ function spawnTest() {
yield clickGraphNode(panelWin, nodeIds[1], true);
ok(WebAudioInspectorView.isVisible(), "InspectorView visible after selecting a node.");
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[1], "InspectorView has correct node set.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set.");
/**
* Reload
@ -36,13 +36,13 @@ function spawnTest() {
let nodeIds = actors.map(actor => actor.actorID);
ok(!WebAudioInspectorView.isVisible(), "InspectorView hidden on start.");
ise(WebAudioInspectorView.getCurrentNode(), null,
ise(WebAudioInspectorView.getCurrentAudioNode(), null,
"InspectorView has no current node set on reset.");
yield clickGraphNode(panelWin, nodeIds[2], true);
ok(WebAudioInspectorView.isVisible(),
"InspectorView visible after selecting a node after a reset.");
is(WebAudioInspectorView.getCurrentNode().id, nodeIds[2], "InspectorView has correct node set upon clicking graph node after a reset.");
is(WebAudioInspectorView.getCurrentAudioNode().id, nodeIds[2], "InspectorView has correct node set upon clicking graph node after a reset.");
yield teardown(panel);
finish();

View File

@ -0,0 +1,27 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
let i = 0;
let ctx = new AudioContext();
let osc = ctx.createOscillator();
let gain = ctx.createGain();
gain.gain.value = 0;
gain.connect(ctx.destination);
osc.start(0);
setInterval(() => ++i && (i % 2 ? osc.connect(gain) : osc.disconnect()), 1000);
</script>
</body>
</html>

View File

@ -27,6 +27,7 @@ const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
// All tests are asynchronous.
waitForExplicitFinish();

View File

@ -203,7 +203,7 @@ let WebAudioGraphView = {
// Override Dagre-d3's post render function by passing in our own.
// This way we can leave styles out of it.
renderer.postRender(function (graph, root) {
renderer.postRender((graph, root) => {
// We have to manually set the marker styling since we cannot
// do this currently with CSS, although it is in spec for SVG2
// https://svgwg.org/svg2-draft/painting.html#VertexMarkerProperties
@ -229,6 +229,12 @@ let WebAudioGraphView = {
.attr("d", "M 0 0 L 10 5 L 0 10 z");
}
// Reselect the previously selected audio node
let currentNode = WebAudioInspectorView.getCurrentAudioNode();
if (currentNode) {
this.focusNode(currentNode.id);
}
// Fire an event upon completed rendering
window.emit(EVENTS.UI_GRAPH_RENDERED, AudioNodes.length, edges.length);
});
@ -425,7 +431,7 @@ let WebAudioInspectorView = {
/**
* Returns the current AudioNodeView.
*/
getCurrentNode: function () {
getCurrentAudioNode: function () {
return this._currentNode;
},

View File

@ -138,6 +138,8 @@ let UI = {
this.updateProjectButton();
this.updateProjectEditorHeader();
break;
case "install-progress":
this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
};
},
@ -169,34 +171,71 @@ let UI = {
}
},
/********** BUSY UI **********/
_busyTimeout: null,
_busyOperationDescription: null,
_busyPromise: null,
updateProgress: function(percent) {
let progress = document.querySelector("#action-busy-determined");
progress.mode = "determined";
progress.value = percent;
this.setupBusyTimeout();
},
busy: function() {
this.hidePanels();
document.querySelector("window").classList.add("busy")
let win = document.querySelector("window");
win.classList.add("busy")
win.classList.add("busy-undetermined");
this.updateCommands();
},
unbusy: function() {
document.querySelector("window").classList.remove("busy")
let win = document.querySelector("window");
win.classList.remove("busy")
win.classList.remove("busy-determined");
win.classList.remove("busy-undetermined");
this.updateCommands();
this._busyPromise = null;
},
setupBusyTimeout: function() {
this.cancelBusyTimeout();
this._busyTimeout = setTimeout(() => {
this.unbusy();
UI.reportError("error_operationTimeout", this._busyOperationDescription);
this._busyPromise.reject("promise timeout: " + this._busyOperationDescription);
}, 30000);
},
cancelBusyTimeout: function() {
clearTimeout(this._busyTimeout);
},
busyWithProgressUntil: function(promise, operationDescription) {
this.busyUntil(promise, operationDescription);
let win = document.querySelector("window");
let progress = document.querySelector("#action-busy-determined");
progress.mode = "undetermined";
win.classList.add("busy-determined");
win.classList.remove("busy-undetermined");
},
busyUntil: function(promise, operationDescription) {
// Freeze the UI until the promise is resolved. A 30s timeout
// will unfreeze the UI, just in case the promise never gets
// resolved.
this._busyPromise = promise;
let timeout = setTimeout(() => {
this.unbusy();
UI.reportError("error_operationTimeout", operationDescription);
promise.reject("promise timeout: " + operationDescription);
}, 30000);
this._busyOperationDescription = operationDescription;
this.setupBusyTimeout();
this.busy();
promise.then(() => {
clearTimeout(timeout);
this.cancelBusyTimeout();
this.unbusy();
}, (e) => {
clearTimeout(timeout);
this.cancelBusyTimeout();
UI.reportError("error_operationFail", operationDescription);
console.error(e);
this.unbusy();
@ -396,6 +435,12 @@ let UI = {
return;
}
// Save last project location
if (project.location) {
Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
}
// Make sure the directory exist before we show Project Editor
let forceDetailsOnly = false;
@ -420,10 +465,6 @@ let UI = {
this.getProjectEditor().then(() => {
this.updateProjectEditorHeader();
}, console.error);
if (project.location) {
Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
}
},
/********** DECK **********/
@ -823,12 +864,11 @@ let Cmds = {
play: function() {
switch(AppManager.selectedProject.type) {
case "packaged":
return UI.busyWithProgressUntil(AppManager.installAndRunProject(), "installing and running app");
case "hosted":
return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
break;
case "runtimeApp":
return UI.busyUntil(AppManager.runRuntimeApp(), "running app");
break;
}
return promise.reject();
},

View File

@ -102,7 +102,10 @@
<toolbarbutton id="action-button-play" class="action-button" command="cmd_play" tooltiptext="&projectMenu_play_label;"/>
<toolbarbutton id="action-button-stop" class="action-button" command="cmd_stop" tooltiptext="&projectMenu_stop_label;"/>
<toolbarbutton id="action-button-debug" class="action-button" command="cmd_toggleToolbox" tooltiptext="&projectMenu_debug_label;"/>
<html:img id="action-busy" src="chrome://webide/skin/throbber.svg"/>
<hbox id="action-busy" align="center">
<html:img id="action-busy-undetermined" src="chrome://webide/skin/throbber.svg"/>
<progressmeter id="action-busy-determined"/>
</hbox>
</hbox>
<hbox id="panel-buttons-container">

View File

@ -59,11 +59,15 @@ exports.AppManager = AppManager = {
this.trackWiFiRuntimes();
this.trackSimulatorRuntimes();
this.onInstallProgress = this.onInstallProgress.bind(this);
AppActorFront.on("install-progress", this.onInstallProgress);
this.observe = this.observe.bind(this);
Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
},
uninit: function() {
AppActorFront.off("install-progress", this.onInstallProgress);
this._unlistenToApps();
this.selectedProject = null;
this.selectedRuntime = null;
@ -134,6 +138,10 @@ exports.AppManager = AppManager = {
this.update("connection");
},
onInstallProgress: function(event, details) {
this.update("install-progress", details);
},
onWebAppsStoreready: function() {
this.update("runtime-apps-found");
},

View File

@ -21,13 +21,15 @@
pointer-events: auto;
}
#action-busy {
#action-busy-undetermined {
height: 24px;
width: 24px;
}
window.busy .action-button,
window:not(.busy) #action-busy {
window:not(.busy) #action-busy,
window.busy-undetermined #action-busy-determined,
window.busy-determined #action-busy-undetermined {
display: none;
}

View File

@ -3920,6 +3920,7 @@ MOZ_ANDROID_HISTORY=
MOZ_WEBSMS_BACKEND=
MOZ_ANDROID_BEAM=
MOZ_LOCALE_SWITCHER=
MOZ_ANDROID_SEARCH_ACTIVITY=
ACCESSIBILITY=1
MOZ_TIME_MANAGER=
MOZ_PAY=
@ -4943,6 +4944,13 @@ if test -n "$MOZ_ANDROID_BEAM"; then
AC_DEFINE(MOZ_ANDROID_BEAM)
fi
dnl ========================================================
dnl = Include Search Activity on Android
dnl ========================================================
if test -n "$MOZ_ANDROID_SEARCH_ACTIVITY"; then
AC_DEFINE(MOZ_ANDROID_SEARCH_ACTIVITY)
fi
dnl ========================================================
dnl = Enable IPDL's "expensive" unit tests
dnl ========================================================
@ -8471,6 +8479,7 @@ AC_SUBST(MOZ_WEBSMS_BACKEND)
AC_SUBST(MOZ_ANDROID_BEAM)
AC_SUBST(MOZ_LOCALE_SWITCHER)
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
AC_SUBST(ENABLE_STRIP)
AC_SUBST(PKG_SKIP_STRIP)
AC_SUBST(STRIP_FLAGS)

View File

@ -17,6 +17,10 @@
#include ../services/manifests/HealthReportAndroidManifest_permissions.xml.in
#include ../services/manifests/SyncAndroidManifest_permissions.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_permissions.xml.in
#endif
#ifndef RELEASE_BUILD
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
@ -306,6 +310,9 @@
#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
#include ../services/manifests/HealthReportAndroidManifest_activities.xml.in
#include ../services/manifests/SyncAndroidManifest_activities.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_activities.xml.in
#endif
#if MOZ_CRASHREPORTER
<activity android:name="org.mozilla.gecko.CrashReporter"
@ -397,6 +404,9 @@
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
#include ../services/manifests/HealthReportAndroidManifest_services.xml.in
#include ../services/manifests/SyncAndroidManifest_services.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_services.xml.in
#endif
</application>

View File

@ -91,6 +91,12 @@ ifdef MOZ_WEBRTC
ALL_JARS += webrtc.jar
endif
ifdef MOZ_ANDROID_SEARCH_ACTIVITY
extra_packages += org.mozilla.search
ALL_JARS += search-activity.jar
generated/org/mozilla/search/R.java: .aapt.deps ;
endif
include $(topsrcdir)/config/config.mk
# Note that we're going to set up a dependency directly between embed_android.dex and the java files

View File

@ -8,6 +8,11 @@ include $(topsrcdir)/config/config.mk
# http://code.google.com/p/android/issues/detail?id=3639
AB_rCD = $(if $(filter he, $(AB_CD)),iw,$(if $(filter id, $(AB_CD)),in,$(subst -,-r,$(AB_CD))))
# The search strings path is always passed to strings.xml.in; the
# decision to include is made based on the feature flag at the
# inclusion site.
SEARCHSTRINGSPATH = $(srcdir)/../../search/strings/search_strings.dtd
SYNCSTRINGSPATH = $(abspath $(call MERGE_FILE,sync_strings.dtd))
STRINGSPATH = $(abspath $(call MERGE_FILE,android_strings.dtd))
ifeq (,$(XPI_NAME))
@ -67,6 +72,7 @@ strings-xml-preqs =\
$(strings-xml-in) \
$(BRANDPATH) \
$(STRINGSPATH) \
$(SEARCHSTRINGSPATH) \
$(SYNCSTRINGSPATH) \
$(BOOKMARKSPATH) \
$(if $(IS_LANGUAGE_REPACK),FORCE) \
@ -86,6 +92,7 @@ $(dir-strings-xml)/strings.xml: $(strings-xml-preqs)
-DMOZ_APP_DISPLAYNAME='@MOZ_APP_DISPLAYNAME@' \
-DSTRINGSPATH='$(STRINGSPATH)' \
-DSYNCSTRINGSPATH='$(SYNCSTRINGSPATH)' \
-DSEARCHSTRINGSPATH='$(SEARCHSTRINGSPATH)' \
$< \
-o $@)

View File

@ -4,3 +4,5 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
DEFINES['MOZ_ANDROID_SEARCH_ACTIVITY'] = 1

View File

@ -527,7 +527,8 @@ ANDROID_GENERATED_RESFILES += [
'res/values/strings.xml',
]
for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL', 'MOZ_DEBUG'):
for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL', 'MOZ_DEBUG',
'MOZ_ANDROID_SEARCH_ACTIVITY'):
if CONFIG[var]:
DEFINES[var] = 1
@ -552,6 +553,26 @@ if '-march=armv7' in CONFIG['OS_CFLAGS']:
else:
DEFINES['MOZ_MIN_CPU_VERSION'] = 5
if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
# The Search Activity is mostly independent of Fennec proper, but
# it does depend on Geckoview. Therefore, we build it as a jar
# that depends on the Geckoview jars.
search_source_dir = SRCDIR + '/../search'
include('../search/search_activity_sources.mozbuild')
ANDROID_RES_DIRS += [search_source_dir + '/res']
resjar.generated_sources += ['org/mozilla/search/R.java']
search_activity = add_java_jar('search-activity')
search_activity.sources += [search_source_dir + '/' + f for f in search_activity_sources]
search_activity.javac_flags += ['-Xlint:all']
search_activity.extra_jars = [
'gecko-R.jar',
'gecko-browser.jar',
'gecko-mozglue.jar',
'gecko-util.jar',
]
generated_recursive_make_targets = ['.aapt.deps', '.locales.deps'] # Captures dependencies on Android manifest and all resources.
generated = add_android_eclipse_library_project('FennecResourcesGenerated')

View File

@ -8,6 +8,9 @@
#includesubst @BRANDPATH@
#includesubst @STRINGSPATH@
#includesubst @SYNCSTRINGSPATH@
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#includesubst @SEARCHSTRINGSPATH@
#endif
<!-- C-style format strings. -->
<!ENTITY formatI "&#037;I">
@ -29,6 +32,11 @@
<string name="moz_android_shared_account_type">@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@</string>
<string name="moz_android_shared_fxaccount_type">@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@</string>
<string name="android_package_name_for_ui">@ANDROID_PACKAGE_NAME@</string>
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/strings/search_strings.xml.in
#endif
#include ../services/strings.xml.in
<string name="no_space_to_start_error">&no_space_to_start_error;</string>
<string name="error_loading_file">&error_loading_file;</string>

View File

@ -368,6 +368,12 @@ public class ToolbarEditText extends CustomEditText
// If we have autocomplete text, the cursor is at the boundary between
// regular and autocomplete text. So regardless of which direction we
// are deleting, we should delete the autocomplete text first.
// Make the IME aware that we interrupted the deleteSurroundingText call,
// by restarting the IME.
final InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
if (imm != null) {
imm.restartInput(ToolbarEditText.this);
}
return false;
}
return super.deleteSurroundingText(beforeLength, afterLength);
@ -386,6 +392,9 @@ public class ToolbarEditText extends CustomEditText
// Make the IME aware that we interrupted the setComposingText call,
// by having finishComposingText() send change notifications to the IME.
finishComposingText();
if (Build.VERSION.SDK_INT >= 9) {
setComposingRegion(composingStart, composingEnd);
}
return true;
}
return false;

View File

@ -74,3 +74,6 @@ MOZ_DEVICES=1
# Enable second screen using native Android libraries
MOZ_NATIVE_DEVICES=
# Don't enable the Search Activity.
# MOZ_ANDROID_SEARCH_ACTIVITY=1

View File

@ -0,0 +1,23 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* This 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.search;
/**
* Key should not be stored here. For more info on storing keys, see
* https://github.com/ericedens/FirefoxSearch/issues/3
*/
public class Constants {
public static final String AUTO_COMPLETE_FRAGMENT = "org.mozilla.search.AUTO_COMPLETE_FRAGMENT";
public static final String CARD_STREAM_FRAGMENT = "org.mozilla.search.CARD_STREAM_FRAGMENT";
public static final String GECKO_VIEW_FRAGMENT = "org.mozilla.search.GECKO_VIEW_FRAGMENT";
public static final String AUTOCOMPLETE_ROW_LIMIT = "5";
}

View File

@ -0,0 +1,100 @@
/* 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.search;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.mozilla.gecko.GeckoView;
import org.mozilla.gecko.GeckoViewChrome;
import org.mozilla.gecko.GeckoViewContent;
import org.mozilla.gecko.PrefsHelper;
public class DetailActivity extends Fragment {
private static final String LOGTAG = "DetailActivity";
private GeckoView geckoView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View mainView = inflater.inflate(R.layout.search_activity_detail, container, false);
geckoView = (GeckoView) mainView.findViewById(R.id.gecko_view);
geckoView.setChromeDelegate(new MyGeckoViewChrome());
geckoView.setContentDelegate(new SearchGeckoView());
PrefsHelper.setPref("privacy.clearOnShutdown.cache", true);
PrefsHelper.setPref("privacy.clearOnShutdown.cookies", true);
if (null == geckoView.getCurrentBrowser()) {
// This pageload allows Fennec to be loaded in a background fragment.
// Without supplying a URL, it doesn't look like Fennec will get loaded?
geckoView.addBrowser("https://search.yahoo.com/search?p=firefox%20android");
}
return mainView;
}
public void setUrl(String url) {
if (null == geckoView.getCurrentBrowser()) {
geckoView.addBrowser(url);
} else {
geckoView.getCurrentBrowser().loadUrl(url);
}
}
private static class MyGeckoViewChrome extends GeckoViewChrome {
@Override
public void onReady(GeckoView view) {
Log.i(LOGTAG, "Gecko is ready");
PrefsHelper.setPref("devtools.debugger.remote-enabled", true);
// The Gecko libraries have finished loading and we can use the rendering engine.
// Let's add a browser (required) and load a page into it.
}
}
private class SearchGeckoView extends GeckoViewContent {
@Override
public void onPageStart(GeckoView geckoView, GeckoView.Browser browser, String s) {
Log.i("OnPageStart", s);
// Only load this page if it's the Yahoo search page that we're using.
// TODO: Make this check more robust, and allow for other search providers.
if (s.contains("//search.yahoo.com")) {
super.onPageStart(geckoView, browser, s);
} else {
browser.stop();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(s));
startActivity(i);
}
}
@Override
public void onPageShow(GeckoView geckoView, GeckoView.Browser browser) {
}
}
}

View File

@ -0,0 +1,103 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.search;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import org.mozilla.search.autocomplete.AcceptsSearchQuery;
import org.mozilla.search.autocomplete.AutoCompleteFragment;
import org.mozilla.search.stream.CardStreamFragment;
/**
* The main entrance for the Android search intent.
* <p/>
* State management is delegated to child fragments. Fragments communicate
* with each other by passing messages through this activity. The only message passing right
* now, the only message passing occurs when a user wants to submit a search query. That
* passes through the onSearch method here.
*/
public class MainActivity extends FragmentActivity implements AcceptsSearchQuery,
FragmentManager.OnBackStackChangedListener {
private DetailActivity detailActivity;
@Override
protected void onCreate(Bundle stateBundle) {
super.onCreate(stateBundle);
// Sets the content view for the Activity
setContentView(R.layout.search_activity_main);
// Gets an instance of the support library FragmentManager
FragmentManager localFragmentManager = getSupportFragmentManager();
// If the incoming state of the Activity is null, sets the initial view to be thumbnails
if (null == stateBundle) {
// Starts a Fragment transaction to track the stack
FragmentTransaction localFragmentTransaction = localFragmentManager.beginTransaction();
localFragmentTransaction.add(R.id.header_fragments, new AutoCompleteFragment(),
Constants.AUTO_COMPLETE_FRAGMENT);
localFragmentTransaction.add(R.id.presearch_fragments, new CardStreamFragment(),
Constants.CARD_STREAM_FRAGMENT);
// Commits this transaction to display the Fragment
localFragmentTransaction.commit();
// The incoming state of the Activity isn't null.
}
}
@Override
protected void onStart() {
super.onStart();
if (null == detailActivity) {
detailActivity = new DetailActivity();
}
if (null == getSupportFragmentManager().findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT)) {
FragmentTransaction txn = getSupportFragmentManager().beginTransaction();
txn.add(R.id.gecko_fragments, detailActivity, Constants.GECKO_VIEW_FRAGMENT);
txn.hide(detailActivity);
txn.commit();
}
}
@Override
public void onSearch(String s) {
FragmentManager localFragmentManager = getSupportFragmentManager();
FragmentTransaction localFragmentTransaction = localFragmentManager.beginTransaction();
localFragmentTransaction
.hide(localFragmentManager.findFragmentByTag(Constants.CARD_STREAM_FRAGMENT))
.addToBackStack(null);
localFragmentTransaction
.show(localFragmentManager.findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT))
.addToBackStack(null);
localFragmentTransaction.commit();
((DetailActivity) getSupportFragmentManager()
.findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT))
.setUrl("https://search.yahoo.com/search?p=" + Uri.encode(s));
}
@Override
public void onBackStackChanged() {
}
}

View File

@ -0,0 +1,15 @@
/* 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.search.autocomplete;
/**
* Allows rows to pass a "jump" event to the parent fragment.
* <p/>
* A jump event is when a user selects a suggestion, but they'd like to continue
* searching. Right now, the UI uses an arrow that points up and to the left.
*/
interface AcceptsJumpTaps {
public void onJumpTap(String suggestion);
}

View File

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.search.autocomplete;
/**
* Allows rows to pass a search event to the parent fragment.
*/
public interface AcceptsSearchQuery {
void onSearch(String s);
}

View File

@ -0,0 +1,45 @@
/* 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.search.autocomplete;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
/**
* The adapter that is used to populate the autocomplete rows.
*/
class AutoCompleteAdapter extends ArrayAdapter<AutoCompleteModel> {
private final AcceptsJumpTaps acceptsJumpTaps;
public AutoCompleteAdapter(Context context, AcceptsJumpTaps acceptsJumpTaps) {
// Uses '0' for the template id since we are overriding getView
// and supplying our own view.
super(context, 0);
this.acceptsJumpTaps = acceptsJumpTaps;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AutoCompleteRowView view;
if (convertView == null) {
view = new AutoCompleteRowView(getContext());
} else {
view = (AutoCompleteRowView) convertView;
}
view.setOnJumpListener(acceptsJumpTaps);
AutoCompleteModel model = getItem(position);
view.setMainText(model.getMainText());
return view;
}
}

View File

@ -0,0 +1,84 @@
/* 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.search.autocomplete;
import android.app.Activity;
import android.database.Cursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.ArrayList;
/**
* A single entry point for querying all agents.
* <p/>
* An agent is responsible for querying some underlying data source. It could be a
* flat file, or a REST endpoint, or a content provider.
*/
class AutoCompleteAgentManager {
private final Handler mainUiHandler;
private final Handler localHandler;
private final AutoCompleteWordListAgent autoCompleteWordListAgent;
public AutoCompleteAgentManager(Activity activity, Handler mainUiHandler) {
HandlerThread thread = new HandlerThread("org.mozilla.search.autocomplete.SuggestionAgent");
// TODO: Where to kill this thread?
thread.start();
Log.i("AUTOCOMPLETE", "Starting thread");
this.mainUiHandler = mainUiHandler;
localHandler = new SuggestionMessageHandler(thread.getLooper());
autoCompleteWordListAgent = new AutoCompleteWordListAgent(activity);
}
/**
* Process the next incoming query.
*/
public void search(String queryString) {
// TODO check if there's a pending search.. not sure how to handle that.
localHandler.sendMessage(localHandler.obtainMessage(0, queryString));
}
/**
* This background thread runs the queries; the results get sent back through mainUiHandler
* <p/>
* TODO: Refactor this wordlist search and add other search providers (eg: Yahoo)
*/
private class SuggestionMessageHandler extends Handler {
private SuggestionMessageHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (null == msg.obj) {
return;
}
Cursor cursor =
autoCompleteWordListAgent.getWordMatches(((String) msg.obj).toLowerCase());
ArrayList<AutoCompleteModel> res = new ArrayList<AutoCompleteModel>();
if (null == cursor) {
return;
}
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
res.add(new AutoCompleteModel(cursor.getString(
cursor.getColumnIndex(AutoCompleteWordListAgent.COL_WORD))));
}
mainUiHandler.sendMessage(Message.obtain(mainUiHandler, 0, res));
}
}
}

View File

@ -0,0 +1,276 @@
/* 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.search.autocomplete;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.mozilla.search.R;
/**
* A fragment to handle autocomplete. Its interface with the outside
* world should be very very limited.
* <p/>
* TODO: Add clear button to search input
* TODO: Add more search providers (other than the dictionary)
*/
public class AutoCompleteFragment extends Fragment implements AdapterView.OnItemClickListener,
TextView.OnEditorActionListener, AcceptsJumpTaps {
private View mainView;
private FrameLayout backdropFrame;
private EditText searchBar;
private ListView suggestionDropdown;
private InputMethodManager inputMethodManager;
private AutoCompleteAdapter autoCompleteAdapter;
private AutoCompleteAgentManager autoCompleteAgentManager;
private State state;
private enum State {
WAITING, // The user is doing something else in the app.
RUNNING // The user is in search mode.
}
public AutoCompleteFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainView = inflater.inflate(R.layout.search_auto_complete, container, false);
backdropFrame = (FrameLayout) mainView.findViewById(R.id.auto_complete_backdrop);
searchBar = (EditText) mainView.findViewById(R.id.auto_complete_search_bar);
suggestionDropdown = (ListView) mainView.findViewById(R.id.auto_complete_dropdown);
inputMethodManager =
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
// Attach a listener for the "search" key on the keyboard.
searchBar.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
autoCompleteAgentManager.search(s.toString());
}
});
searchBar.setOnEditorActionListener(this);
searchBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.hasFocus()) {
return;
}
transitionToRunning();
}
});
backdropFrame.setOnClickListener(new BackdropClickListener());
autoCompleteAdapter = new AutoCompleteAdapter(getActivity(), this);
// Disable notifying on change. We're going to be changing the entire dataset, so
// we don't want multiple re-draws.
autoCompleteAdapter.setNotifyOnChange(false);
suggestionDropdown.setAdapter(autoCompleteAdapter);
initRows();
autoCompleteAgentManager =
new AutoCompleteAgentManager(getActivity(), new MainUiHandler(autoCompleteAdapter));
// This will hide the autocomplete box and background frame.
// Is there a case where we *shouldn't* hide this upfront?
// Uncomment show card stream first.
// transitionToWaiting();
transitionToRunning();
// Attach listener for tapping on a suggestion.
suggestionDropdown.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String query = ((AutoCompleteModel) suggestionDropdown.getItemAtPosition(position))
.getMainText();
startSearch(query);
}
});
return mainView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
inputMethodManager = null;
mainView = null;
searchBar = null;
if (null != suggestionDropdown) {
suggestionDropdown.setOnItemClickListener(null);
suggestionDropdown.setAdapter(null);
suggestionDropdown = null;
}
autoCompleteAdapter = null;
}
/**
* Handler for clicks of individual items.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO: Right now each row has its own click handler.
// Can we
}
/**
* Handler for the "search" button on the keyboard.
*/
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
startSearch(v.getText().toString());
return true;
}
return false;
}
private void initRows() {
// TODO: Query history for these items.
autoCompleteAdapter.add(new AutoCompleteModel("banana"));
autoCompleteAdapter.add(new AutoCompleteModel("cat pics"));
autoCompleteAdapter.add(new AutoCompleteModel("mexican food"));
autoCompleteAdapter.add(new AutoCompleteModel("cuba libre"));
autoCompleteAdapter.notifyDataSetChanged();
}
/**
* Send a search intent and put the widget into waiting.
*/
private void startSearch(String queryString) {
if (getActivity() instanceof AcceptsSearchQuery) {
searchBar.setText(queryString);
searchBar.setSelection(queryString.length());
transitionToWaiting();
((AcceptsSearchQuery) getActivity()).onSearch(queryString);
} else {
throw new RuntimeException("Parent activity does not implement AcceptsSearchQuery.");
}
}
private void transitionToWaiting() {
if (state == State.WAITING) {
return;
}
searchBar.setFocusable(false);
searchBar.setFocusableInTouchMode(false);
searchBar.clearFocus();
inputMethodManager.hideSoftInputFromWindow(searchBar.getWindowToken(), 0);
suggestionDropdown.setVisibility(View.GONE);
backdropFrame.setVisibility(View.GONE);
state = State.WAITING;
}
private void transitionToRunning() {
if (state == State.RUNNING) {
return;
}
searchBar.setFocusable(true);
searchBar.setFocusableInTouchMode(true);
searchBar.requestFocus();
inputMethodManager.showSoftInput(searchBar, InputMethodManager.SHOW_IMPLICIT);
suggestionDropdown.setVisibility(View.VISIBLE);
backdropFrame.setVisibility(View.VISIBLE);
state = State.RUNNING;
}
@Override
public void onJumpTap(String suggestion) {
searchBar.setText(suggestion);
// Move cursor to end of search input.
searchBar.setSelection(suggestion.length());
autoCompleteAgentManager.search(suggestion);
}
/**
* Receives messages from the SuggestionAgent's background thread.
*/
private static class MainUiHandler extends Handler {
final AutoCompleteAdapter autoCompleteAdapter1;
public MainUiHandler(AutoCompleteAdapter autoCompleteAdapter) {
autoCompleteAdapter1 = autoCompleteAdapter;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (null == msg.obj) {
return;
}
if (!(msg.obj instanceof Iterable)) {
return;
}
autoCompleteAdapter1.clear();
for (Object obj : (Iterable) msg.obj) {
if (obj instanceof AutoCompleteModel) {
autoCompleteAdapter1.add((AutoCompleteModel) obj);
}
}
autoCompleteAdapter1.notifyDataSetChanged();
}
}
/**
* Click handler for the backdrop. This should:
* - Remove focus from the search bar
* - Hide the keyboard
* - Hide the backdrop
* - Hide the suggestion box.
*/
private class BackdropClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
transitionToWaiting();
}
}
}

View File

@ -0,0 +1,31 @@
/* 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.search.autocomplete;
/**
* The SuggestionModel is the data model behind the autocomplete rows. Right now it
* only has a text field. In the future, this could be extended to include other
* types of rows. For example, a row that has a URL and the name of a website.
*/
class AutoCompleteModel {
// The text that should immediately jump out to the user;
// for example, the name of a restaurant or the title
// of a website.
private final String mainText;
public AutoCompleteModel(String mainText) {
this.mainText = mainText;
}
public String getMainText() {
return mainText;
}
public String toString() {
return mainText;
}
}

View File

@ -0,0 +1,61 @@
/* 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.search.autocomplete;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.mozilla.search.R;
/**
* One row withing the autocomplete suggestion list.
*/
class AutoCompleteRowView extends LinearLayout {
private TextView textView;
private AcceptsJumpTaps onJumpListener;
public AutoCompleteRowView(Context context) {
super(context);
init();
}
private void init() {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.search_auto_complete_row, this, true);
textView = (TextView) findViewById(R.id.auto_complete_row_text);
findViewById(R.id.auto_complete_row_jump_button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null == onJumpListener) {
Log.e("SuggestionRow.onJump", "jump listener is null");
return;
}
onJumpListener.onJumpTap(textView.getText().toString());
}
});
}
public void setMainText(String s) {
textView.setText(s);
}
public void setOnJumpListener(AcceptsJumpTaps onJumpListener) {
this.onJumpListener = onJumpListener;
}
}

View File

@ -0,0 +1,153 @@
/* 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.search.autocomplete;
import android.app.Activity;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import android.widget.Toast;
import org.mozilla.search.Constants;
import org.mozilla.search.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Helper to search a word dictionary.
* From: https://developer.android.com/training/search/search.html
*/
class AutoCompleteWordListAgent {
public static final String COL_WORD = "WORD";
private static final String TAG = "DictionaryDatabase";
private static final String DATABASE_NAME = "DICTIONARY";
private static final String FTS_VIRTUAL_TABLE = "FTS";
private static final int DATABASE_VERSION = 1;
private final DatabaseOpenHelper databaseOpenHelper;
public AutoCompleteWordListAgent(Activity activity) {
databaseOpenHelper = new DatabaseOpenHelper(activity);
// DB helper uses lazy initialization, so this forces the db helper to start indexing the
// wordlist
databaseOpenHelper.getReadableDatabase();
}
public Cursor getWordMatches(String query) {
String selection = COL_WORD + " MATCH ?";
String[] selectionArgs = new String[]{query + "*"};
return query(selection, selectionArgs);
}
private Cursor query(String selection, String[] selectionArgs) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(FTS_VIRTUAL_TABLE);
Cursor cursor = builder.query(databaseOpenHelper.getReadableDatabase(), null, selection,
selectionArgs, null, null, null, Constants.AUTOCOMPLETE_ROW_LIMIT);
if (cursor == null) {
return null;
} else if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
private static class DatabaseOpenHelper extends SQLiteOpenHelper {
private final Activity activity;
private SQLiteDatabase database;
private static final String FTS_TABLE_CREATE =
"CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE + " USING fts3 (" + COL_WORD + ")";
DatabaseOpenHelper(Activity activity) {
super(activity, DATABASE_NAME, null, DATABASE_VERSION);
this.activity = activity;
}
@Override
public void onCreate(SQLiteDatabase db) {
database = db;
database.execSQL(FTS_TABLE_CREATE);
loadDictionary();
}
private void loadDictionary() {
new Thread(new Runnable() {
public void run() {
try {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "Starting post-install indexing",
Toast.LENGTH_SHORT).show();
Toast.makeText(activity,
"Don't worry; Mark & Ian we'll figure out a way around " +
"this :)", Toast.LENGTH_SHORT
).show();
}
});
loadWords();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "All done!", Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private void loadWords() throws IOException {
final Resources resources = activity.getResources();
InputStream inputStream = resources.openRawResource(R.raw.en_us);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String sql = "INSERT INTO " + FTS_VIRTUAL_TABLE + " VALUES (?);";
SQLiteStatement statement = database.compileStatement(sql);
database.beginTransaction();
try {
String line;
while (null != (line = reader.readLine())) {
statement.clearBindings();
statement.bindString(1, line.trim());
statement.execute();
}
} finally {
database.setTransactionSuccessful();
database.endTransaction();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion +
", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
onCreate(db);
}
}
}

View File

@ -0,0 +1,61 @@
/* 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.search.stream;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import org.mozilla.search.R;
/**
* This fragment is responsible for managing the card stream. Right now
* we only use this during pre-search, but we could also use it
* during post-search at some point.
*/
public class CardStreamFragment extends ListFragment {
private ArrayAdapter<PreloadAgent.TmpItem> adapter;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public CardStreamFragment() {
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setDivider(null);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
View headerView = getLayoutInflater(savedInstanceState)
.inflate(R.layout.search_stream_header, getListView(), false);
getListView().addHeaderView(headerView, null, false);
if (null == adapter) {
adapter = new ArrayAdapter<PreloadAgent.TmpItem>(getActivity(), R.layout.search_card,
R.id.card_title, PreloadAgent.ITEMS) {
/**
* Return false here disables the ListView from highlighting the click events
* for each of the items. Each card should handle its own click events.
*/
@Override
public boolean isEnabled(int position) {
return false;
}
};
}
setListAdapter(adapter);
}
}

View File

@ -0,0 +1,48 @@
/* 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.search.stream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A temporary agent for loading cards into the pre-load card stream.
* <p/>
* When we have more agents, we'll want to put an agent manager between the CardStreamFragment
* and the set of all agents. See autocomplete.AutoCompleteFragmentManager.
*/
class PreloadAgent {
public static final List<TmpItem> ITEMS = new ArrayList<TmpItem>();
private static final Map<String, TmpItem> ITEM_MAP = new HashMap<String, TmpItem>();
static {
addItem(new TmpItem("1", "Pre-load item1"));
addItem(new TmpItem("2", "Pre-load item2"));
}
private static void addItem(TmpItem item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
public static class TmpItem {
public final String id;
public final String content;
public TmpItem(String id, String content) {
this.id = id;
this.content = content;
}
@Override
public String toString() {
return content;
}
}
}

View File

@ -0,0 +1,20 @@
<activity
android:name="org.mozilla.search.MainActivity"
android:label="@string/search_app_name"
android:theme="@style/AppTheme"
android:screenOrientation="portrait">
<!-- Add this to activity declaration to hide keyboard on launch -->
<!-- android:windowSoftInputMode="stateHidden" -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ASSIST"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

View File

@ -0,0 +1,2 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,48 @@
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding
android:bottom="5dp"
android:left="10dp"
android:right="10dp"
android:top="10dp"/>
<solid android:color="@color/transparent"/>
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="0dp"
android:right="0dp"
android:top="0dp"/>
<solid android:color="@color/card_shadow_1"/>
<corners android:radius="2dp"/>
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="0dp"
android:right="0dp"
android:top="0dp"/>
<solid android:color="@color/card_shadow_2"/>
<corners android:radius="2dp"/>
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="@color/card_background"/>
<corners android:radius="2dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,20 @@
<!-- 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/. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="org.mozilla.search.DetailActivity">
<org.mozilla.gecko.GeckoView
android:id="@+id/gecko_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</RelativeLayout>

View File

@ -0,0 +1,35 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/gecko_fragments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="45dp"
/>
<FrameLayout
android:id="@+id/presearch_fragments"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<FrameLayout
android:id="@+id/header_fragments"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</merge>

View File

@ -0,0 +1,38 @@
<!-- 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/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".autocomplete.AutoCompleteFragment"
>
<FrameLayout
android:id="@+id/auto_complete_backdrop"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#a71f1f1f"
android:clickable="true"
android:focusable="true"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/search_card_background"
android:orientation="vertical"
>
<EditText
android:id="@+id/auto_complete_search_bar"
style="@style/AutoCompleteEditText"/>
<ListView
android:id="@+id/auto_complete_dropdown"
style="@style/AutoCompleteDropdown"/>
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,27 @@
<!-- 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="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
<TextView
android:id="@+id/auto_complete_row_text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"/>
<Button
android:id="@+id/auto_complete_row_jump_button"
style="@style/BorderLessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_jump_arrow"/>
</LinearLayout>

View File

@ -0,0 +1,18 @@
<!-- 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="match_parent"
android:layout_height="120dp"
android:background="@drawable/search_card_background"
android:orientation="vertical"
>
<TextView
android:id="@+id/card_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

View File

@ -0,0 +1,12 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/search_header"
android:contentDescription="@string/search_header_image_content_description">
</ImageView>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar"/>
<style name="BorderLessButton">
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
</resources>

View File

@ -0,0 +1,15 @@
<!-- 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/. -->
<resources>
<color name="transparent">#00FFFFFF</color>
<!-- card colors -->
<color name="card_background">#ffffff</color>
<color name="card_shadow_1">#d4d4d4</color>
<color name="card_shadow_2">#dddddd</color>
</resources>

View File

@ -0,0 +1,9 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -0,0 +1,31 @@
<!-- 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/. -->
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="@android:style/Theme.Light.NoTitleBar">
</style>
<style name="BorderLessButton">
</style>
<style name="AutoCompleteDropdown">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">#fff</item>
<item name="android:layout_margin">10dp</item>
</style>
<style name="AutoCompleteEditText">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:imeOptions">actionSearch</item>
<item name="android:inputType">textNoSuggestions|textVisiblePassword</item>
</style>
</resources>

View File

@ -0,0 +1,21 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
search_activity_sources = [
'java/org/mozilla/search/autocomplete/AcceptsJumpTaps.java',
'java/org/mozilla/search/autocomplete/AcceptsSearchQuery.java',
'java/org/mozilla/search/autocomplete/AutoCompleteAdapter.java',
'java/org/mozilla/search/autocomplete/AutoCompleteAgentManager.java',
'java/org/mozilla/search/autocomplete/AutoCompleteFragment.java',
'java/org/mozilla/search/autocomplete/AutoCompleteModel.java',
'java/org/mozilla/search/autocomplete/AutoCompleteRowView.java',
'java/org/mozilla/search/autocomplete/AutoCompleteWordListAgent.java',
'java/org/mozilla/search/Constants.java',
'java/org/mozilla/search/DetailActivity.java',
'java/org/mozilla/search/MainActivity.java',
'java/org/mozilla/search/stream/CardStreamFragment.java',
'java/org/mozilla/search/stream/PreloadAgent.java',
]

View File

@ -0,0 +1,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY search_jump_arrow '&#8598;'>
<!ENTITY search_app_name 'Firefox Search'>
<!ENTITY search_header_image_content_description 'Firefox Search Header Image'>

View File

@ -0,0 +1,3 @@
<string name="search_app_name">&search_app_name;</string>
<string name="search_jump_arrow">&search_jump_arrow;</string>
<string name="search_header_image_content_description">&search_header_image_content_description;</string>

View File

@ -198,6 +198,12 @@ user_pref('toolkit.telemetry.server', 'https://%(server)s/telemetry-dummy/');
// resolves and accepts requests, even if they all fail.
user_pref('identity.fxaccounts.auth.uri', 'https://%(server)s/fxa-dummy/');
// Ditto for all the other Firefox accounts URIs used for about:accounts et al.:
user_pref("identity.fxaccounts.remote.signup.uri", "https://%(server)s/fxa-signup");
user_pref("identity.fxaccounts.remote.force_auth.uri", "https://%(server)s/fxa-force-auth");
user_pref("identity.fxaccounts.remote.signin.uri", "https://%(server)s/fxa-signin");
user_pref("identity.fxaccounts.settings.uri", "https://%(server)s/fxa-settings");
// Enable logging of APZ test data (see bug 961289).
user_pref('apz.test.logging_enabled', true);

View File

@ -16,6 +16,13 @@ Cu.import("resource://gre/modules/FileUtils.jsm");
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
DevToolsUtils.defineLazyGetter(this, "AppFrames", () => {
try {
return Cu.import("resource://gre/modules/AppFrames.jsm", {}).AppFrames;
} catch(e) {}
return null;
});
function debug(aMsg) {
/*
Cc["@mozilla.org/consoleservice;1"]
@ -841,22 +848,11 @@ WebappsActor.prototype = {
},
_appFrames: function () {
// For now, we only support app frames on b2g
if (Services.appinfo.ID != "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
return;
}
// Register the system app
let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
let systemAppFrame = chromeWindow.shell.contentBrowser;
yield systemAppFrame;
// Register apps hosted in the system app. i.e. the homescreen, all regular
// apps and the keyboard.
// Bookmark apps and other system app internal frames like captive portal
// are also hosted in system app, but they are not using mozapp attribute.
let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
for (let i = 0; i < frames.length; i++) {
yield frames[i];
// Try to filter on b2g and mulet
if (AppFrames) {
return AppFrames.list();
} else {
return [];
}
},
@ -866,9 +862,14 @@ WebappsActor.prototype = {
let appPromises = [];
let apps = [];
for each (let frame in this._appFrames()) {
for (let frame of this._appFrames()) {
let manifestURL = frame.getAttribute("mozapp");
// _appFrames can return more than one frame with the same manifest url
if (apps.indexOf(manifestURL) != -1) {
continue;
}
appPromises.push(this._isAppAllowedForURL(manifestURL).then(allowed => {
if (allowed) {
apps.push(manifestURL);
@ -884,13 +885,24 @@ WebappsActor.prototype = {
getAppActor: function ({ manifestURL }) {
debug("getAppActor\n");
// Connects to the main app frame, whose `name` attribute
// is set to 'main' by gaia. If for any reason, gaia doesn't set any
// frame as main, no frame matches, then we connect arbitrary
// to the first app frame...
let appFrame = null;
for each (let frame in this._appFrames()) {
let frames = [];
for (let frame of this._appFrames()) {
if (frame.getAttribute("mozapp") == manifestURL) {
appFrame = frame;
break;
if (frame.name == "main") {
appFrame = frame;
break;
}
frames.push(frame);
}
}
if (!appFrame && frames.length > 0) {
appFrame = frames[0];
}
let notFoundError = {
error: "appNotFound",
@ -931,13 +943,9 @@ WebappsActor.prototype = {
},
watchApps: function () {
this._openedApps = new Set();
// For now, app open/close events are only implement on b2g
if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
let systemAppFrame = chromeWindow.getContentWindow();
systemAppFrame.addEventListener("appwillopen", this);
systemAppFrame.addEventListener("appterminated", this);
if (AppFrames) {
AppFrames.addObserver(this);
}
Services.obs.addObserver(this, "webapps-installed", false);
Services.obs.addObserver(this, "webapps-uninstall", false);
@ -946,12 +954,8 @@ WebappsActor.prototype = {
},
unwatchApps: function () {
this._openedApps = null;
if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
let systemAppFrame = chromeWindow.getContentWindow();
systemAppFrame.removeEventListener("appwillopen", this);
systemAppFrame.removeEventListener("appterminated", this);
if (AppFrames) {
AppFrames.removeObserver(this);
}
Services.obs.removeObserver(this, "webapps-installed", false);
Services.obs.removeObserver(this, "webapps-uninstall", false);
@ -959,46 +963,46 @@ WebappsActor.prototype = {
return {};
},
handleEvent: function (event) {
let manifestURL;
switch(event.type) {
case "appwillopen":
manifestURL = event.detail.manifestURL;
// Ignore the event if we already received an appwillopen for this app
// (appwillopen is also fired when the app has been moved to background
// and get back to foreground)
if (this._openedApps.has(manifestURL)) {
return;
}
this._openedApps.add(manifestURL);
this._isAppAllowedForURL(manifestURL).then(allowed => {
if (allowed) {
this.conn.send({ from: this.actorID,
type: "appOpen",
manifestURL: manifestURL
});
}
});
break;
case "appterminated":
manifestURL = event.detail.manifestURL;
this._openedApps.delete(manifestURL);
this._isAppAllowedForURL(manifestURL).then(allowed => {
if (allowed) {
this.conn.send({ from: this.actorID,
type: "appClose",
manifestURL: manifestURL
});
}
});
break;
onAppFrameCreated: function (frame, isFirstAppFrame) {
if (!isFirstAppFrame) {
return;
}
let manifestURL = frame.appManifestURL;
// Only track app frames
if (!manifestURL) {
return;
}
this._isAppAllowedForURL(manifestURL).then(allowed => {
if (allowed) {
this.conn.send({ from: this.actorID,
type: "appOpen",
manifestURL: manifestURL
});
}
});
},
onAppFrameDestroyed: function (frame, isLastAppFrame) {
if (!isLastAppFrame) {
return;
}
let manifestURL = frame.appManifestURL;
// Only track app frames
if (!manifestURL) {
return;
}
this._isAppAllowedForURL(manifestURL).then(allowed => {
if (allowed) {
this.conn.send({ from: this.actorID,
type: "appClose",
manifestURL: manifestURL
});
}
});
},
observe: function (subject, topic, data) {

View File

@ -96,26 +96,6 @@ addTest(function findCssSelector() {
runNextTest();
});
addTest(function getBackgroundImageUriFromProperty() {
let data = [
["background: url(foo.png);", "foo.png"],
["background: url(\"foo.png\") ", "foo.png"],
["background: url('foo.png') ; ", "foo.png"],
["background: foo.png", null],
["background: url()", ""],
];
for (let i = 0; i < data.length; i++) {
let prop = data[i][0];
let result = data[i][1];
is (CssLogic.getBackgroundImageUriFromProperty(prop), result,
"Background image matches for index " + i);
}
runNextTest();
});
</script>
</head>
<body>

View File

@ -814,42 +814,6 @@ CssLogic.shortSource = function CssLogic_shortSource(aSheet)
return dataUrl ? dataUrl[1] : aSheet.href;
}
/**
* Extract the background image URL (if any) from a property value.
* Used, for example, for the preview tooltip in the rule view and
* computed view.
*
* @param {String} aProperty
* @param {String} aSheetHref
* @return {string} a image URL
*/
CssLogic.getBackgroundImageUriFromProperty = function(aProperty, aSheetHref) {
let startToken = "url(", start = aProperty.indexOf(startToken), end;
if (start === -1) {
return null;
}
aProperty = aProperty.substring(start + startToken.length).trim();
let quote = aProperty.substring(0, 1);
if (quote === "'" || quote === '"') {
end = aProperty.search(new RegExp(quote + "\\s*\\)"));
start = 1;
} else {
end = aProperty.indexOf(")");
start = 0;
}
let uri = aProperty.substring(start, end).trim();
if (aSheetHref) {
let IOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let sheetUri = IOService.newURI(aSheetHref, null, null);
uri = sheetUri.resolve(uri);
}
return uri;
}
/**
* Find the position of [element] in [nodeList].
* @returns an index of the match, or -1 if there is no match