mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-24 11:27:49 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
7ccae990fa
@ -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) {
|
||||
|
136
b2g/components/AppFrames.jsm
Normal file
136
b2g/components/AppFrames.jsm
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -45,6 +45,7 @@ if CONFIG['MOZ_UPDATER']:
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AlertsHelper.jsm',
|
||||
'AppFrames.jsm',
|
||||
'ContentRequestHelper.jsm',
|
||||
'ErrorPage.jsm',
|
||||
'SignInToWebsite.jsm',
|
||||
|
@ -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|",
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
@ -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]
|
||||
|
@ -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.");
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
27
browser/devtools/webaudioeditor/test/doc_connect-toggle.html
Normal file
27
browser/devtools/webaudioeditor/test/doc_connect-toggle.html
Normal 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>
|
@ -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();
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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">
|
||||
|
@ -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");
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 $@)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -8,6 +8,9 @@
|
||||
#includesubst @BRANDPATH@
|
||||
#includesubst @STRINGSPATH@
|
||||
#includesubst @SYNCSTRINGSPATH@
|
||||
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
|
||||
#includesubst @SEARCHSTRINGSPATH@
|
||||
#endif
|
||||
|
||||
<!-- C-style format strings. -->
|
||||
<!ENTITY formatI "%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>
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
23
mobile/android/search/java/org/mozilla/search/Constants.java
Normal file
23
mobile/android/search/java/org/mozilla/search/Constants.java
Normal 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";
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
103
mobile/android/search/java/org/mozilla/search/MainActivity.java
Normal file
103
mobile/android/search/java/org/mozilla/search/MainActivity.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -0,0 +1,2 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
BIN
mobile/android/search/res/drawable-hdpi/search_header.png
Normal file
BIN
mobile/android/search/res/drawable-hdpi/search_header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
mobile/android/search/res/drawable-mdpi/search_header.png
Normal file
BIN
mobile/android/search/res/drawable-mdpi/search_header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
mobile/android/search/res/drawable-xhdpi/search_header.png
Normal file
BIN
mobile/android/search/res/drawable-xhdpi/search_header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
@ -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>
|
20
mobile/android/search/res/layout/search_activity_detail.xml
Normal file
20
mobile/android/search/res/layout/search_activity_detail.xml
Normal 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>
|
35
mobile/android/search/res/layout/search_activity_main.xml
Normal file
35
mobile/android/search/res/layout/search_activity_main.xml
Normal 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>
|
38
mobile/android/search/res/layout/search_auto_complete.xml
Normal file
38
mobile/android/search/res/layout/search_auto_complete.xml
Normal 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>
|
@ -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>
|
18
mobile/android/search/res/layout/search_card.xml
Normal file
18
mobile/android/search/res/layout/search_card.xml
Normal 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>
|
12
mobile/android/search/res/layout/search_stream_header.xml
Normal file
12
mobile/android/search/res/layout/search_stream_header.xml
Normal 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>
|
46988
mobile/android/search/res/raw/en_us.txt
Normal file
46988
mobile/android/search/res/raw/en_us.txt
Normal file
File diff suppressed because it is too large
Load Diff
13
mobile/android/search/res/values-v13/search_styles.xml
Normal file
13
mobile/android/search/res/values-v13/search_styles.xml
Normal 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>
|
15
mobile/android/search/res/values/search_colors.xml
Normal file
15
mobile/android/search/res/values/search_colors.xml
Normal 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>
|
9
mobile/android/search/res/values/search_dimens.xml
Normal file
9
mobile/android/search/res/values/search_dimens.xml
Normal 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>
|
31
mobile/android/search/res/values/search_styles.xml
Normal file
31
mobile/android/search/res/values/search_styles.xml
Normal 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>
|
21
mobile/android/search/search_activity_sources.mozbuild
Normal file
21
mobile/android/search/search_activity_sources.mozbuild
Normal 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',
|
||||
]
|
7
mobile/android/search/strings/search_strings.dtd
Normal file
7
mobile/android/search/strings/search_strings.dtd
Normal 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 '↖'>
|
||||
<!ENTITY search_app_name 'Firefox Search'>
|
||||
<!ENTITY search_header_image_content_description 'Firefox Search Header Image'>
|
3
mobile/android/search/strings/search_strings.xml.in
Normal file
3
mobile/android/search/strings/search_strings.xml.in
Normal 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>
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user