зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound.
This commit is contained in:
Коммит
c768f2741b
|
@ -3015,11 +3015,11 @@ const BrowserSearch = {
|
|||
* allows the search service to provide a different nsISearchSubmission
|
||||
* depending on e.g. where the search is triggered in the UI.
|
||||
*
|
||||
* @return string Name of the search engine used to perform a search or null
|
||||
* if a search was not performed.
|
||||
* @return engine The search engine used to perform a search, or null if no
|
||||
* search was performed.
|
||||
*/
|
||||
loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
|
||||
var engine;
|
||||
_loadSearch: function (searchText, useNewTab, purpose) {
|
||||
let engine;
|
||||
|
||||
// If the search bar is visible, use the current engine, otherwise, fall
|
||||
// back to the default engine.
|
||||
|
@ -3028,7 +3028,7 @@ const BrowserSearch = {
|
|||
else
|
||||
engine = Services.search.defaultEngine;
|
||||
|
||||
var submission = engine.getSubmission(searchText, null, purpose); // HTML response
|
||||
let submission = engine.getSubmission(searchText, null, purpose); // HTML response
|
||||
|
||||
// getSubmission can return null if the engine doesn't have a URL
|
||||
// with a text/html response type. This is unlikely (since
|
||||
|
@ -3045,6 +3045,20 @@ const BrowserSearch = {
|
|||
inBackground: inBackground,
|
||||
relatedToCurrent: true });
|
||||
|
||||
return engine;
|
||||
},
|
||||
|
||||
/**
|
||||
* Just like _loadSearch, but preserving an old API.
|
||||
*
|
||||
* @return string Name of the search engine used to perform a search or null
|
||||
* if a search was not performed.
|
||||
*/
|
||||
loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
|
||||
let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
|
||||
if (!engine) {
|
||||
return null;
|
||||
}
|
||||
return engine.name;
|
||||
},
|
||||
|
||||
|
@ -3055,7 +3069,7 @@ const BrowserSearch = {
|
|||
* BrowserSearch.loadSearch for the preferred API.
|
||||
*/
|
||||
loadSearchFromContext: function (terms) {
|
||||
let engine = BrowserSearch.loadSearch(terms, true, "contextmenu");
|
||||
let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
|
||||
if (engine) {
|
||||
BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
|
||||
}
|
||||
|
@ -3081,8 +3095,7 @@ const BrowserSearch = {
|
|||
* FHR records only search counts and nothing pertaining to the search itself.
|
||||
*
|
||||
* @param engine
|
||||
* (string) The name of the engine used to perform the search. This
|
||||
* is typically nsISearchEngine.name.
|
||||
* (nsISearchEngine) The engine handling the search.
|
||||
* @param source
|
||||
* (string) Where the search originated from. See the FHR
|
||||
* SearchesProvider for allowed values.
|
||||
|
|
|
@ -486,7 +486,7 @@ function getNumberOfSearches(aEngineName) {
|
|||
let provider = reporter.getProvider("org.mozilla.searches");
|
||||
ok(provider, "Searches provider is available.");
|
||||
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
return m.getValues().then(data => {
|
||||
let now = new Date();
|
||||
let yday = new Date(now);
|
||||
|
|
|
@ -47,7 +47,7 @@ function test() {
|
|||
let provider = reporter.getProvider("org.mozilla.searches");
|
||||
ok(provider, "Searches provider is available.");
|
||||
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
m.getValues().then(function onValues(data) {
|
||||
let now = new Date();
|
||||
ok(data.days.hasDay(now), "Have data for today.");
|
||||
|
|
|
@ -23,7 +23,7 @@ function test() {
|
|||
reporter.onInit().then(function onInit() {
|
||||
let provider = reporter.getProvider("org.mozilla.searches");
|
||||
ok(provider, "Searches provider is available.");
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
|
||||
m.getValues().then(function onData(data) {
|
||||
let now = new Date();
|
||||
|
|
|
@ -320,9 +320,8 @@ BrowserGlue.prototype = {
|
|||
|
||||
reporter.onInit().then(function record() {
|
||||
try {
|
||||
let name = subject.QueryInterface(Ci.nsISearchEngine).name;
|
||||
reporter.getProvider("org.mozilla.searches").recordSearch(name,
|
||||
"urlbar");
|
||||
let engine = subject.QueryInterface(Ci.nsISearchEngine);
|
||||
reporter.getProvider("org.mozilla.searches").recordSearch(engine, "urlbar");
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
|
|
|
@ -483,7 +483,7 @@
|
|||
|
||||
// null parameter below specifies HTML response for search
|
||||
var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
|
||||
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
|
||||
BrowserSearch.recordSearchInHealthReport(this.currentEngine, "searchbar");
|
||||
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
|
||||
]]></body>
|
||||
</method>
|
||||
|
|
|
@ -27,14 +27,15 @@ function test() {
|
|||
ok(reporter, "Health Reporter available.");
|
||||
reporter.onInit().then(function onInit() {
|
||||
let provider = reporter.getProvider("org.mozilla.searches");
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
|
||||
m.getValues().then(function onData(data) {
|
||||
let now = new Date();
|
||||
let oldCount = 0;
|
||||
|
||||
// Foo engine goes into "other" bucket.
|
||||
let field = "other.searchbar";
|
||||
// Find the right bucket for the "Foo" engine.
|
||||
let engine = Services.search.getEngineByName("Foo");
|
||||
let field = (engine.identifier || "other-Foo") + ".searchbar";
|
||||
|
||||
if (data.days.hasDay(now)) {
|
||||
let day = data.days.getDay(now);
|
||||
|
|
|
@ -306,7 +306,20 @@ TabTarget.prototype = {
|
|||
this._client.connect((aType, aTraits) => {
|
||||
this._client.listTabs(aResponse => {
|
||||
this._root = aResponse;
|
||||
this._form = aResponse.tabs[aResponse.selected];
|
||||
|
||||
let windowUtils = this.window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let outerWindow = windowUtils.outerWindowID;
|
||||
aResponse.tabs.some((tab) => {
|
||||
if (tab.outerWindowID === outerWindow) {
|
||||
this._form = tab;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!this._form) {
|
||||
this._form = aResponse.tabs[aResponse.selected];
|
||||
}
|
||||
attachTab();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -165,11 +165,12 @@ let AboutHome = {
|
|||
Cu.reportError(ex);
|
||||
break;
|
||||
}
|
||||
let engine = Services.search.currentEngine;
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
|
||||
window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome");
|
||||
#endif
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = Services.search.currentEngine.getSubmission(data.searchTerms, null, "homepage");
|
||||
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
|
||||
window.loadURI(submission.uri.spec, null, submission.postData);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -643,8 +643,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
|
|||
nsRect* aDisplayPort,
|
||||
nsRect* aCriticalDisplayPort,
|
||||
ViewID aScrollId,
|
||||
const nsDisplayItem::ContainerParameters& aContainerParameters,
|
||||
bool aMayHaveTouchListeners) {
|
||||
const nsDisplayItem::ContainerParameters& aContainerParameters) {
|
||||
nsPresContext* presContext = aForFrame->PresContext();
|
||||
int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
|
||||
LayoutDeviceToLayerScale resolution(aContainerParameters.mXScale, aContainerParameters.mYScale);
|
||||
|
@ -719,7 +718,16 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
|
|||
metrics.mZoom = metrics.mCumulativeResolution * metrics.mDevPixelsPerCSSPixel
|
||||
* layerToScreenScale;
|
||||
|
||||
metrics.mMayHaveTouchListeners = aMayHaveTouchListeners;
|
||||
if (presShell) {
|
||||
nsIDocument* document = nullptr;
|
||||
document = presShell->GetDocument();
|
||||
if (document) {
|
||||
nsCOMPtr<nsPIDOMWindow> innerWin(document->GetInnerWindow());
|
||||
if (innerWin) {
|
||||
metrics.mMayHaveTouchListeners = innerWin->HasTouchEventListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the composition bounds as the size of the scroll frame and
|
||||
// its origin relative to the reference frame.
|
||||
|
@ -1248,14 +1256,6 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
|
|||
}
|
||||
}
|
||||
|
||||
bool mayHaveTouchListeners = false;
|
||||
if (document) {
|
||||
nsCOMPtr<nsPIDOMWindow> innerWin(document->GetInnerWindow());
|
||||
if (innerWin) {
|
||||
mayHaveTouchListeners = innerWin->HasTouchEventListeners();
|
||||
}
|
||||
}
|
||||
|
||||
nsRect viewport(aBuilder->ToReferenceFrame(aForFrame), aForFrame->GetSize());
|
||||
|
||||
RecordFrameMetrics(aForFrame, rootScrollFrame,
|
||||
|
@ -1263,7 +1263,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
|
|||
root, mVisibleRect, viewport,
|
||||
(usingDisplayport ? &displayport : nullptr),
|
||||
(usingCriticalDisplayport ? &criticalDisplayport : nullptr),
|
||||
id, containerParameters, mayHaveTouchListeners);
|
||||
id, containerParameters);
|
||||
if (usingDisplayport &&
|
||||
!(root->GetContentFlags() & Layer::CONTENT_OPAQUE)) {
|
||||
// See bug 693938, attachment 567017
|
||||
|
@ -3576,7 +3576,7 @@ nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
|
|||
mVisibleRect, viewport,
|
||||
(usingDisplayport ? &displayport : nullptr),
|
||||
(usingCriticalDisplayport ? &criticalDisplayport : nullptr),
|
||||
scrollId, aContainerParameters, false);
|
||||
scrollId, aContainerParameters);
|
||||
|
||||
return layer.forget();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.mozilla.gecko.health.BrowserHealthReporter;
|
|||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.SearchEngine;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.prompts.Prompt;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
|
@ -167,6 +168,15 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
private BrowserHealthReporter mBrowserHealthReporter;
|
||||
|
||||
// The tab to be selected on editing mode exit.
|
||||
private Integer mTargetTabForEditingMode = null;
|
||||
|
||||
// The animator used to toggle HomePager visibility has a race where if the HomePager is shown
|
||||
// (starting the animation), the HomePager is hidden, and the HomePager animation completes,
|
||||
// both the web content and the HomePager will be hidden. This flag is used to prevent the
|
||||
// race by determining if the web content should be hidden at the animation's end.
|
||||
private boolean mHideWebContentOnAnimationEnd = false;
|
||||
|
||||
private SiteIdentityPopup mSiteIdentityPopup;
|
||||
|
||||
public SiteIdentityPopup getSiteIdentityPopup() {
|
||||
|
@ -481,7 +491,11 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
mBrowserToolbar.setOnStopEditingListener(new BrowserToolbar.OnStopEditingListener() {
|
||||
public void onStopEditing() {
|
||||
// Re-enable doorhanger notifications.
|
||||
selectTargetTabForEditingMode();
|
||||
hideHomePager();
|
||||
hideBrowserSearch();
|
||||
|
||||
// Re-enable doorhanger notifications. They may trigger on the selected tab above.
|
||||
mDoorHangerPopup.enable();
|
||||
}
|
||||
});
|
||||
|
@ -1301,32 +1315,29 @@ abstract public class BrowserApp extends GeckoApp
|
|||
return false;
|
||||
}
|
||||
|
||||
// If this tab is already selected, just hide the home pager.
|
||||
if (tabs.isSelectedTabId(tabId)) {
|
||||
hideHomePager();
|
||||
} else {
|
||||
tabs.selectTab(tabId);
|
||||
}
|
||||
// Set the target tab to null so it does not get selected (on editing
|
||||
// mode exit) in lieu of the tab we are about to select.
|
||||
mTargetTabForEditingMode = null;
|
||||
Tabs.getInstance().selectTab(tabId);
|
||||
|
||||
hideBrowserSearch();
|
||||
mBrowserToolbar.cancelEdit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openUrl(String url) {
|
||||
openUrl(url, null, false);
|
||||
private void openUrlAndStopEditing(String url) {
|
||||
openUrlAndStopEditing(url, null, false);
|
||||
}
|
||||
|
||||
private void openUrl(String url, boolean newTab) {
|
||||
openUrl(url, null, newTab);
|
||||
private void openUrlAndStopEditing(String url, boolean newTab) {
|
||||
openUrlAndStopEditing(url, null, newTab);
|
||||
}
|
||||
|
||||
private void openUrl(String url, String searchEngine) {
|
||||
openUrl(url, searchEngine, false);
|
||||
private void openUrlAndStopEditing(String url, String searchEngine) {
|
||||
openUrlAndStopEditing(url, searchEngine, false);
|
||||
}
|
||||
|
||||
private void openUrl(String url, String searchEngine, boolean newTab) {
|
||||
private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
|
||||
mBrowserToolbar.setProgressVisibility(true);
|
||||
|
||||
int flags = Tabs.LOADURL_NONE;
|
||||
|
@ -1336,7 +1347,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);
|
||||
|
||||
hideBrowserSearch();
|
||||
mBrowserToolbar.cancelEdit();
|
||||
}
|
||||
|
||||
|
@ -1419,6 +1429,9 @@ abstract public class BrowserApp extends GeckoApp
|
|||
throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode");
|
||||
}
|
||||
|
||||
final Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
||||
mTargetTabForEditingMode = (selectedTab != null ? selectedTab.getId() : null);
|
||||
|
||||
final PropertyAnimator animator = new PropertyAnimator(250);
|
||||
animator.setUseHardwareLayer(false);
|
||||
|
||||
|
@ -1434,8 +1447,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
final String url = mBrowserToolbar.commitEdit();
|
||||
hideHomePager();
|
||||
hideBrowserSearch();
|
||||
|
||||
// Don't do anything if the user entered an empty URL.
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
|
@ -1485,15 +1496,18 @@ abstract public class BrowserApp extends GeckoApp
|
|||
/**
|
||||
* Record in Health Report that a search has occurred.
|
||||
*
|
||||
* @param identifier
|
||||
* a search identifier, such as "partnername". Can be null.
|
||||
* @param engine
|
||||
* a search engine instance. Can be null.
|
||||
* @param where
|
||||
* where the search was initialized; one of the values in
|
||||
* {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
|
||||
*/
|
||||
private static void recordSearch(String identifier, String where) {
|
||||
Log.i(LOGTAG, "Recording search: " + identifier + ", " + where);
|
||||
private static void recordSearch(SearchEngine engine, String where) {
|
||||
Log.i(LOGTAG, "Recording search: " +
|
||||
((engine == null) ? "null" : engine.name) +
|
||||
", " + where);
|
||||
try {
|
||||
String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
|
||||
message.put("location", where);
|
||||
|
@ -1509,13 +1523,13 @@ abstract public class BrowserApp extends GeckoApp
|
|||
return false;
|
||||
}
|
||||
|
||||
mBrowserToolbar.cancelEdit();
|
||||
|
||||
// Resetting the visibility of HomePager, which might have been hidden
|
||||
// by the filterEditingMode().
|
||||
// cancelEdit will call hideHomePager. If we're on web content, this is fine. If we're on
|
||||
// about:home, the HomePager needs to be visible in the end (note that hideHomePager will
|
||||
// not hide the HomePager on about:home). However, filterEditingMode may have hidden the
|
||||
// HomePager so we set it visible here.
|
||||
mHomePager.setVisibility(View.VISIBLE);
|
||||
hideHomePager();
|
||||
hideBrowserSearch();
|
||||
|
||||
mBrowserToolbar.cancelEdit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1531,6 +1545,22 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the target tab for editing mode. This is expected to be the tab selected on editing
|
||||
* mode entry, unless it is subsequently overridden.
|
||||
*
|
||||
* A background tab may be selected while editing mode is active (e.g. popups), causing the
|
||||
* new url to load in the newly selected tab. Call this method on editing mode exit to
|
||||
* mitigate this.
|
||||
*/
|
||||
private void selectTargetTabForEditingMode() {
|
||||
if (mTargetTabForEditingMode != null) {
|
||||
Tabs.getInstance().selectTab(mTargetTabForEditingMode);
|
||||
}
|
||||
|
||||
mTargetTabForEditingMode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the home pager for the given tab.
|
||||
*/
|
||||
|
@ -1574,7 +1604,38 @@ abstract public class BrowserApp extends GeckoApp
|
|||
final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
|
||||
mHomePager = (HomePager) homePagerStub.inflate();
|
||||
}
|
||||
|
||||
mHomePager.show(getSupportFragmentManager(), page, animator);
|
||||
|
||||
// Hide the web content so it cannot be focused by screen readers.
|
||||
hideWebContentOnPropertyAnimationEnd(animator);
|
||||
}
|
||||
|
||||
private void hideWebContentOnPropertyAnimationEnd(final PropertyAnimator animator) {
|
||||
if (animator == null) {
|
||||
hideWebContent();
|
||||
return;
|
||||
}
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
mHideWebContentOnAnimationEnd = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
if (mHideWebContentOnAnimationEnd) {
|
||||
hideWebContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideWebContent() {
|
||||
// The view is set to INVISIBLE, rather than GONE, to avoid
|
||||
// the additional requestLayout() call.
|
||||
mLayerView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void hideHomePager() {
|
||||
|
@ -1587,6 +1648,12 @@ abstract public class BrowserApp extends GeckoApp
|
|||
return;
|
||||
}
|
||||
|
||||
// Prevent race in hiding web content - see declaration for more info.
|
||||
mHideWebContentOnAnimationEnd = false;
|
||||
|
||||
// Display the previously hidden web content (which prevented screen reader access).
|
||||
mLayerView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mHomePager != null) {
|
||||
mHomePager.hide();
|
||||
}
|
||||
|
@ -2293,7 +2360,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
for (String url : urls) {
|
||||
if (!maybeSwitchToTab(url, flags)) {
|
||||
openUrl(url, true);
|
||||
openUrlAndStopEditing(url, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2302,15 +2369,15 @@ abstract public class BrowserApp extends GeckoApp
|
|||
@Override
|
||||
public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
|
||||
if (!maybeSwitchToTab(url, flags)) {
|
||||
openUrl(url);
|
||||
openUrlAndStopEditing(url);
|
||||
}
|
||||
}
|
||||
|
||||
// BrowserSearch.OnSearchListener
|
||||
@Override
|
||||
public void onSearch(String engineId, String text) {
|
||||
recordSearch(engineId, "barsuggest");
|
||||
openUrl(text, engineId);
|
||||
public void onSearch(SearchEngine engine, String text) {
|
||||
recordSearch(engine, "barsuggest");
|
||||
openUrlAndStopEditing(text, engine.name);
|
||||
}
|
||||
|
||||
// BrowserSearch.OnEditSuggestionListener
|
||||
|
|
|
@ -410,6 +410,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
|||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// Drop the virtual keyboard.
|
||||
clearFocus();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,11 @@ public final class BitmapUtils {
|
|||
return GeckoJarReader.getBitmapDrawable(context.getResources(), data);
|
||||
}
|
||||
|
||||
// Don't attempt to validate the JAR signature when loading an add-on icon
|
||||
if (data.startsWith("jar:file")) {
|
||||
return GeckoJarReader.getBitmapDrawable(context.getResources(), Uri.decode(data));
|
||||
}
|
||||
|
||||
URL url = new URL(data);
|
||||
InputStream is = (InputStream) url.getContent();
|
||||
try {
|
||||
|
|
|
@ -741,7 +741,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
*/
|
||||
|
||||
public static final String MEASUREMENT_NAME_SEARCH_COUNTS = "org.mozilla.searches.counts";
|
||||
public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 4;
|
||||
public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 5;
|
||||
|
||||
public static final String[] SEARCH_LOCATIONS = {
|
||||
"barkeyword",
|
||||
|
@ -749,84 +749,6 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
"bartext",
|
||||
};
|
||||
|
||||
// See services/healthreport/providers.jsm. Sorry for the duplication.
|
||||
// THIS LIST MUST BE SORTED per java.lang.Comparable<String>.
|
||||
private static final String[] SEARCH_PROVIDERS = {
|
||||
"amazon-co-uk",
|
||||
"amazon-de",
|
||||
"amazon-en-GB",
|
||||
"amazon-france",
|
||||
"amazon-it",
|
||||
"amazon-jp",
|
||||
"amazondotcn",
|
||||
"amazondotcom",
|
||||
"amazondotcom-de",
|
||||
|
||||
"aol-en-GB",
|
||||
"aol-web-search",
|
||||
|
||||
"bing",
|
||||
|
||||
"eBay",
|
||||
"eBay-de",
|
||||
"eBay-en-GB",
|
||||
"eBay-es",
|
||||
"eBay-fi",
|
||||
"eBay-france",
|
||||
"eBay-hu",
|
||||
"eBay-in",
|
||||
"eBay-it",
|
||||
|
||||
"google",
|
||||
"google-jp",
|
||||
"google-ku",
|
||||
"google-maps-zh-TW",
|
||||
|
||||
"mailru",
|
||||
|
||||
"mercadolibre-ar",
|
||||
"mercadolibre-cl",
|
||||
"mercadolibre-mx",
|
||||
|
||||
"seznam-cz",
|
||||
|
||||
"twitter",
|
||||
"twitter-de",
|
||||
"twitter-ja",
|
||||
|
||||
"wikipedia", // Manually added.
|
||||
|
||||
"yahoo",
|
||||
"yahoo-NO",
|
||||
"yahoo-answer-zh-TW",
|
||||
"yahoo-ar",
|
||||
"yahoo-bid-zh-TW",
|
||||
"yahoo-br",
|
||||
"yahoo-ch",
|
||||
"yahoo-cl",
|
||||
"yahoo-de",
|
||||
"yahoo-en-GB",
|
||||
"yahoo-es",
|
||||
"yahoo-fi",
|
||||
"yahoo-france",
|
||||
"yahoo-fy-NL",
|
||||
"yahoo-id",
|
||||
"yahoo-in",
|
||||
"yahoo-it",
|
||||
"yahoo-jp",
|
||||
"yahoo-jp-auctions",
|
||||
"yahoo-mx",
|
||||
"yahoo-sv-SE",
|
||||
"yahoo-zh-TW",
|
||||
|
||||
"yandex",
|
||||
"yandex-ru",
|
||||
"yandex-slovari",
|
||||
"yandex-tr",
|
||||
"yandex.by",
|
||||
"yandex.ru-be",
|
||||
};
|
||||
|
||||
private void initializeSearchProvider() {
|
||||
this.storage.ensureMeasurementInitialized(
|
||||
MEASUREMENT_NAME_SEARCH_COUNTS,
|
||||
|
@ -854,30 +776,13 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
this.dispatcher.registerEventListener(EVENT_SEARCH, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the field key for the search provider. This turns null and
|
||||
* non-partner providers into "other".
|
||||
*
|
||||
* @param engine an engine identifier, such as "yandex"
|
||||
* @return the key to use, such as "other" or "yandex".
|
||||
*/
|
||||
protected String getEngineKey(final String engine) {
|
||||
if (engine == null) {
|
||||
return "other";
|
||||
}
|
||||
|
||||
// This is inefficient. Optimize if necessary.
|
||||
boolean found = (0 <= java.util.Arrays.binarySearch(SEARCH_PROVIDERS, engine));
|
||||
return found ? engine : "other";
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a search.
|
||||
*
|
||||
* @param engine the string identifier for the engine, or null if it's not a partner.
|
||||
* @param engineID the string identifier for the engine. Can be <code>null</code>.
|
||||
* @param location one of a fixed set of locations: see {@link #SEARCH_LOCATIONS}.
|
||||
*/
|
||||
public void recordSearch(final String engine, final String location) {
|
||||
public void recordSearch(final String engineID, final String location) {
|
||||
if (this.state != State.INITIALIZED) {
|
||||
Log.d(LOG_TAG, "Not initialized: not recording search. (" + this.state + ")");
|
||||
return;
|
||||
|
@ -889,7 +794,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
|
||||
final int day = storage.getDay();
|
||||
final int env = this.env;
|
||||
final String key = getEngineKey(engine);
|
||||
final String key = (engineID == null) ? "other" : engineID;
|
||||
final BrowserHealthRecorder self = this;
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
|
|
|
@ -13,8 +13,8 @@ import org.mozilla.gecko.R;
|
|||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.SearchEngine;
|
||||
import org.mozilla.gecko.home.SearchLoader.SearchCursorLoader;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
@ -132,7 +132,7 @@ public class BrowserSearch extends HomeFragment
|
|||
private View mSuggestionsOptInPrompt;
|
||||
|
||||
public interface OnSearchListener {
|
||||
public void onSearch(String engineId, String text);
|
||||
public void onSearch(SearchEngine engine, String text);
|
||||
}
|
||||
|
||||
public interface OnEditSuggestionListener {
|
||||
|
@ -393,7 +393,7 @@ public class BrowserSearch extends HomeFragment
|
|||
}
|
||||
|
||||
private void setSuggestions(ArrayList<String> suggestions) {
|
||||
mSearchEngines.get(0).suggestions = suggestions;
|
||||
mSearchEngines.get(0).setSuggestions(suggestions);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
@ -417,14 +417,11 @@ public class BrowserSearch extends HomeFragment
|
|||
ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
|
||||
for (int i = 0; i < engines.length(); i++) {
|
||||
final JSONObject engineJSON = engines.getJSONObject(i);
|
||||
final String name = engineJSON.getString("name");
|
||||
final String identifier = engineJSON.getString("identifier");
|
||||
final String iconURI = engineJSON.getString("iconURI");
|
||||
final Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconURI);
|
||||
final SearchEngine engine = new SearchEngine(engineJSON);
|
||||
|
||||
if (name.equals(suggestEngine) && suggestTemplate != null) {
|
||||
// Suggest engine should be at the front of the list
|
||||
searchEngines.add(0, new SearchEngine(name, identifier, icon));
|
||||
if (engine.name.equals(suggestEngine) && suggestTemplate != null) {
|
||||
// Suggest engine should be at the front of the list.
|
||||
searchEngines.add(0, engine);
|
||||
|
||||
// The only time Tabs.getInstance().getSelectedTab() should
|
||||
// be null is when we're restoring after a crash. We should
|
||||
|
@ -441,7 +438,7 @@ public class BrowserSearch extends HomeFragment
|
|||
SUGGESTION_TIMEOUT, SUGGESTION_MAX);
|
||||
}
|
||||
} else {
|
||||
searchEngines.add(new SearchEngine(name, identifier, icon));
|
||||
searchEngines.add(engine);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -713,7 +710,7 @@ public class BrowserSearch extends HomeFragment
|
|||
// row contains multiple items, clicking the row will do nothing.
|
||||
final int index = getEngineIndex(position);
|
||||
if (index != -1) {
|
||||
return mSearchEngines.get(index).suggestions.isEmpty();
|
||||
return !mSearchEngines.get(index).hasSuggestions();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -744,7 +741,7 @@ public class BrowserSearch extends HomeFragment
|
|||
row.setSearchTerm(mSearchTerm);
|
||||
|
||||
final SearchEngine engine = mSearchEngines.get(getEngineIndex(position));
|
||||
final boolean animate = (mAnimateSuggestions && engine.suggestions.size() > 0);
|
||||
final boolean animate = (mAnimateSuggestions && engine.hasSuggestions());
|
||||
row.updateFromSearchEngine(engine, animate);
|
||||
if (animate) {
|
||||
// Only animate suggestions the first time they are shown
|
||||
|
@ -877,13 +874,13 @@ public class BrowserSearch extends HomeFragment
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
// Dismiss the soft keyboard.
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(event);
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,13 @@ public class HomePager extends ViewPager {
|
|||
// This is to keep all 4 pages in memory after they are
|
||||
// selected in the pager.
|
||||
setOffscreenPageLimit(3);
|
||||
|
||||
// We can call HomePager.requestFocus to steal focus from the URL bar and drop the soft
|
||||
// keyboard. However, if there are no focusable views (e.g. an empty reading list), the
|
||||
// URL bar will be refocused. Therefore, we make the HomePager container focusable to
|
||||
// ensure there is always a focusable view. This would ordinarily be done via an XML
|
||||
// attribute, but it is not working properly.
|
||||
setFocusableInTouchMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -324,10 +331,7 @@ public class HomePager extends ViewPager {
|
|||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
// XXX: Drop the soft keyboard by stealing focus. Note that the HomePager (via XML
|
||||
// attr) is focusable after its descendants allowing requestFocus to succeed and drop
|
||||
// the soft keyboard even if there are no other focusable views on the screen (e.g.
|
||||
// the Reading List is empty).
|
||||
// Drop the soft keyboard by stealing focus from the URL bar.
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,25 +5,89 @@
|
|||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class SearchEngine {
|
||||
public String name;
|
||||
public String identifier;
|
||||
public Bitmap icon;
|
||||
public ArrayList<String> suggestions;
|
||||
public class SearchEngine {
|
||||
public static final String LOG_TAG = "GeckoSearchEngine";
|
||||
|
||||
public SearchEngine(String name, String identifier) {
|
||||
this(name, identifier, null);
|
||||
public final String name; // Never null.
|
||||
public final String identifier; // Can be null.
|
||||
|
||||
private final Bitmap icon;
|
||||
private volatile List<String> suggestions = new ArrayList<String>(); // Never null.
|
||||
|
||||
public SearchEngine(JSONObject engineJSON) throws JSONException {
|
||||
if (engineJSON == null) {
|
||||
throw new IllegalArgumentException("Can't instantiate SearchEngine from null JSON.");
|
||||
}
|
||||
|
||||
this.name = getString(engineJSON, "name");
|
||||
if (this.name == null) {
|
||||
throw new IllegalArgumentException("Cannot have an unnamed search engine.");
|
||||
}
|
||||
|
||||
this.identifier = getString(engineJSON, "identifier");
|
||||
|
||||
final String iconURI = getString(engineJSON, "iconURI");
|
||||
if (iconURI == null) {
|
||||
Log.w(LOG_TAG, "iconURI is null for search engine " + this.name);
|
||||
this.icon = null;
|
||||
return;
|
||||
}
|
||||
this.icon = BitmapUtils.getBitmapFromDataURI(iconURI);
|
||||
}
|
||||
|
||||
public SearchEngine(String name, String identifier, Bitmap icon) {
|
||||
this.name = name;
|
||||
this.identifier = identifier;
|
||||
this.icon = icon;
|
||||
this.suggestions = new ArrayList<String>();
|
||||
private static String getString(JSONObject data, String key) throws JSONException {
|
||||
if (data.isNull(key)) {
|
||||
return null;
|
||||
}
|
||||
return data.getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a non-null string suitable for use by FHR.
|
||||
*/
|
||||
public String getEngineIdentifier() {
|
||||
if (this.identifier != null) {
|
||||
return this.identifier;
|
||||
}
|
||||
if (this.name != null) {
|
||||
return "other-" + this.name;
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
public boolean hasSuggestions() {
|
||||
return !this.suggestions.isEmpty();
|
||||
}
|
||||
|
||||
public int getSuggestionsCount() {
|
||||
return this.suggestions.size();
|
||||
}
|
||||
|
||||
public Iterable<String> getSuggestions() {
|
||||
return this.suggestions;
|
||||
}
|
||||
|
||||
public void setSuggestions(List<String> suggestions) {
|
||||
if (suggestions == null) {
|
||||
this.suggestions = new ArrayList<String>();
|
||||
return;
|
||||
}
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
public Bitmap getIcon() {
|
||||
return this.icon;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
|
||||
}
|
||||
} else if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, suggestion);
|
||||
mSearchListener.onSearch(mSearchEngine, suggestion);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -135,7 +135,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
public void performUserEnteredSearch() {
|
||||
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
|
||||
if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, searchTerm);
|
||||
mSearchListener.onSearch(mSearchEngine, searchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,25 +162,25 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
}
|
||||
|
||||
public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
|
||||
// Update search engine reference
|
||||
// Update search engine reference.
|
||||
mSearchEngine = searchEngine;
|
||||
|
||||
// Set the search engine icon (e.g., Google) for the row
|
||||
mIconView.updateAndScaleImage(mSearchEngine.icon, mSearchEngine.name);
|
||||
// Set the search engine icon (e.g., Google) for the row.
|
||||
mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier());
|
||||
|
||||
// Set the initial content description
|
||||
// Set the initial content description.
|
||||
setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
|
||||
|
||||
// Add additional suggestions given by this engine
|
||||
// Add additional suggestions given by this engine.
|
||||
final int recycledSuggestionCount = mSuggestionView.getChildCount();
|
||||
final int suggestionCount = mSearchEngine.suggestions.size();
|
||||
|
||||
for (int i = 0; i < suggestionCount; i++) {
|
||||
int suggestionCounter = 0;
|
||||
for (String suggestion : mSearchEngine.getSuggestions()) {
|
||||
final View suggestionItem;
|
||||
|
||||
// Reuse suggestion views from recycled view, if possible
|
||||
if (i + 1 < recycledSuggestionCount) {
|
||||
suggestionItem = mSuggestionView.getChildAt(i + 1);
|
||||
// Reuse suggestion views from recycled view, if possible.
|
||||
if (suggestionCounter + 1 < recycledSuggestionCount) {
|
||||
suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1);
|
||||
suggestionItem.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
|
||||
|
@ -195,23 +195,24 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
mSuggestionView.addView(suggestionItem);
|
||||
}
|
||||
|
||||
final String suggestion = mSearchEngine.suggestions.get(i);
|
||||
setSuggestionOnView(suggestionItem, suggestion);
|
||||
|
||||
if (animate) {
|
||||
AlphaAnimation anim = new AlphaAnimation(0, 1);
|
||||
anim.setDuration(ANIMATION_DURATION);
|
||||
anim.setStartOffset(i * ANIMATION_DURATION);
|
||||
anim.setStartOffset(suggestionCounter * ANIMATION_DURATION);
|
||||
suggestionItem.startAnimation(anim);
|
||||
}
|
||||
|
||||
++suggestionCounter;
|
||||
}
|
||||
|
||||
// Hide extra suggestions that have been recycled
|
||||
for (int i = suggestionCount + 1; i < recycledSuggestionCount; i++) {
|
||||
// Hide extra suggestions that have been recycled.
|
||||
for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) {
|
||||
mSuggestionView.getChildAt(i).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Make sure mSelectedView is still valid
|
||||
// Make sure mSelectedView is still valid.
|
||||
if (mSelectedView >= mSuggestionView.getChildCount()) {
|
||||
mSelectedView = mSuggestionView.getChildCount() - 1;
|
||||
}
|
||||
|
|
|
@ -55,20 +55,25 @@
|
|||
android:layout_alignParentBottom="true">
|
||||
</RelativeLayout>
|
||||
|
||||
<org.mozilla.gecko.BrowserToolbar android:id="@+id/browser_toolbar"
|
||||
<FrameLayout android:id="@+id/search_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_below="@+id/browser_toolbar"
|
||||
android:background="@android:color/white"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<!-- When focus is cleared from from BrowserToolbar's EditText to
|
||||
lower the virtual keyboard, focus will be returned to the root
|
||||
view. To make sure the EditText is not the first focusable view in
|
||||
the root view, BrowserToolbar should be specified as low in the
|
||||
view hierarchy as possible. -->
|
||||
<org.mozilla.gecko.BrowserToolbar android:id="@id/browser_toolbar"
|
||||
style="@style/BrowserToolbar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="@dimen/browser_toolbar_height"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
|
||||
<FrameLayout android:id="@+id/search_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_below="@id/browser_toolbar"
|
||||
android:background="@android:color/white"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</view>
|
||||
|
||||
<LinearLayout android:id="@+id/toast"
|
||||
|
|
|
@ -1186,71 +1186,20 @@ SearchCountMeasurement1.prototype = Object.freeze({
|
|||
* We don't use the search engine name directly, because it is shared across
|
||||
* locales; e.g., eBay-de and eBay both share the name "eBay".
|
||||
*/
|
||||
function SearchCountMeasurement2() {
|
||||
this._fieldSpecs = null;
|
||||
this._interestingEngines = null; // Name -> ID. ("Amazon.com" -> "amazondotcom")
|
||||
|
||||
function SearchCountMeasurementBase() {
|
||||
this._fieldSpecs = {};
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
SearchCountMeasurement2.prototype = Object.freeze({
|
||||
SearchCountMeasurementBase.prototype = Object.freeze({
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
name: "counts",
|
||||
version: 2,
|
||||
|
||||
/**
|
||||
* Default implementation; can be overridden by test helpers.
|
||||
*/
|
||||
getDefaultEngines: function () {
|
||||
return Services.search.getDefaultEngines();
|
||||
},
|
||||
|
||||
_initialize: function () {
|
||||
// Don't create all of these for every profile.
|
||||
// There are 61 partner engines, translating to 244 fields.
|
||||
// Instead, compute only those that are possible -- those for whom the
|
||||
// provider is one of the default search engines.
|
||||
// This set can grow over time, and change as users run different localized
|
||||
// Firefox instances.
|
||||
this._fieldSpecs = {};
|
||||
this._interestingEngines = {};
|
||||
|
||||
for (let source of this.SOURCES) {
|
||||
this._fieldSpecs["other." + source] = DAILY_COUNTER_FIELD;
|
||||
}
|
||||
|
||||
let engines = this.getDefaultEngines();
|
||||
for (let engine of engines) {
|
||||
let id = engine.identifier;
|
||||
if (!id || (this.PROVIDERS.indexOf(id) == -1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._interestingEngines[engine.name] = id;
|
||||
let fieldPrefix = id + ".";
|
||||
for (let source of this.SOURCES) {
|
||||
this._fieldSpecs[fieldPrefix + source] = DAILY_COUNTER_FIELD;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Our fields are dynamic, so we compute them into _fieldSpecs by looking at
|
||||
// the current set of interesting engines.
|
||||
// Our fields are dynamic.
|
||||
get fields() {
|
||||
if (!this._fieldSpecs) {
|
||||
this._initialize();
|
||||
}
|
||||
return this._fieldSpecs;
|
||||
},
|
||||
|
||||
get interestingEngines() {
|
||||
if (!this._fieldSpecs) {
|
||||
this._initialize();
|
||||
}
|
||||
return this._interestingEngines;
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior: serializers should include every counter
|
||||
* field from the DB, even if we don't currently have it registered.
|
||||
|
@ -1280,101 +1229,6 @@ SearchCountMeasurement2.prototype = Object.freeze({
|
|||
return Metrics.Storage.FIELD_DAILY_COUNTER;
|
||||
},
|
||||
|
||||
// You can compute the total list of fields by unifying the entire l10n repo
|
||||
// set with the list of partners:
|
||||
//
|
||||
// sort -u */*/searchplugins/list.txt | tr -d '^M' | uniq | grep -f partners.txt
|
||||
//
|
||||
// where partners.txt contains
|
||||
//
|
||||
// amazon
|
||||
// aol
|
||||
// bing
|
||||
// eBay
|
||||
// google
|
||||
// mailru
|
||||
// mercadolibre
|
||||
// seznam
|
||||
// twitter
|
||||
// yahoo
|
||||
// yandex
|
||||
//
|
||||
// Please update this list as the set of partners changes.
|
||||
//
|
||||
PROVIDERS: [
|
||||
"amazon-co-uk",
|
||||
"amazon-de",
|
||||
"amazon-en-GB",
|
||||
"amazon-france",
|
||||
"amazon-it",
|
||||
"amazon-jp",
|
||||
"amazondotcn",
|
||||
"amazondotcom",
|
||||
"amazondotcom-de",
|
||||
|
||||
"aol-en-GB",
|
||||
"aol-web-search",
|
||||
|
||||
"bing",
|
||||
|
||||
"eBay",
|
||||
"eBay-de",
|
||||
"eBay-en-GB",
|
||||
"eBay-es",
|
||||
"eBay-fi",
|
||||
"eBay-france",
|
||||
"eBay-hu",
|
||||
"eBay-in",
|
||||
"eBay-it",
|
||||
|
||||
"google",
|
||||
"google-jp",
|
||||
"google-ku",
|
||||
"google-maps-zh-TW",
|
||||
|
||||
"mailru",
|
||||
|
||||
"mercadolibre-ar",
|
||||
"mercadolibre-cl",
|
||||
"mercadolibre-mx",
|
||||
|
||||
"seznam-cz",
|
||||
|
||||
"twitter",
|
||||
"twitter-de",
|
||||
"twitter-ja",
|
||||
|
||||
"yahoo",
|
||||
"yahoo-NO",
|
||||
"yahoo-answer-zh-TW",
|
||||
"yahoo-ar",
|
||||
"yahoo-bid-zh-TW",
|
||||
"yahoo-br",
|
||||
"yahoo-ch",
|
||||
"yahoo-cl",
|
||||
"yahoo-de",
|
||||
"yahoo-en-GB",
|
||||
"yahoo-es",
|
||||
"yahoo-fi",
|
||||
"yahoo-france",
|
||||
"yahoo-fy-NL",
|
||||
"yahoo-id",
|
||||
"yahoo-in",
|
||||
"yahoo-it",
|
||||
"yahoo-jp",
|
||||
"yahoo-jp-auctions",
|
||||
"yahoo-mx",
|
||||
"yahoo-sv-SE",
|
||||
"yahoo-zh-TW",
|
||||
|
||||
"yandex",
|
||||
"yandex-ru",
|
||||
"yandex-slovari",
|
||||
"yandex-tr",
|
||||
"yandex.by",
|
||||
"yandex.ru-be",
|
||||
],
|
||||
|
||||
SOURCES: [
|
||||
"abouthome",
|
||||
"contextmenu",
|
||||
|
@ -1383,6 +1237,40 @@ SearchCountMeasurement2.prototype = Object.freeze({
|
|||
],
|
||||
});
|
||||
|
||||
function SearchCountMeasurement2() {
|
||||
SearchCountMeasurementBase.call(this);
|
||||
}
|
||||
|
||||
SearchCountMeasurement2.prototype = Object.freeze({
|
||||
__proto__: SearchCountMeasurementBase.prototype,
|
||||
name: "counts",
|
||||
version: 2,
|
||||
});
|
||||
|
||||
function SearchCountMeasurement3() {
|
||||
SearchCountMeasurementBase.call(this);
|
||||
}
|
||||
|
||||
SearchCountMeasurement3.prototype = Object.freeze({
|
||||
__proto__: SearchCountMeasurementBase.prototype,
|
||||
name: "counts",
|
||||
version: 3,
|
||||
|
||||
getEngines: function () {
|
||||
return Services.search.getEngines();
|
||||
},
|
||||
|
||||
getEngineID: function (engine) {
|
||||
if (!engine) {
|
||||
return "other";
|
||||
}
|
||||
if (engine.identifier) {
|
||||
return engine.identifier;
|
||||
}
|
||||
return "other-" + engine.name;
|
||||
},
|
||||
});
|
||||
|
||||
this.SearchesProvider = function () {
|
||||
Metrics.Provider.call(this);
|
||||
};
|
||||
|
@ -1394,6 +1282,7 @@ this.SearchesProvider.prototype = Object.freeze({
|
|||
measurementTypes: [
|
||||
SearchCountMeasurement1,
|
||||
SearchCountMeasurement2,
|
||||
SearchCountMeasurement3,
|
||||
],
|
||||
|
||||
/**
|
||||
|
@ -1412,8 +1301,7 @@ this.SearchesProvider.prototype = Object.freeze({
|
|||
* Record that a search occurred.
|
||||
*
|
||||
* @param engine
|
||||
* (string) The search engine used. If the search engine is unknown,
|
||||
* the search will be attributed to "other".
|
||||
* (nsISearchEngine) The search engine used.
|
||||
* @param source
|
||||
* (string) Where the search was initiated from. Must be one of the
|
||||
* SearchCountMeasurement2.SOURCES values.
|
||||
|
@ -1422,17 +1310,30 @@ this.SearchesProvider.prototype = Object.freeze({
|
|||
* The promise is resolved when the storage operation completes.
|
||||
*/
|
||||
recordSearch: function (engine, source) {
|
||||
let m = this.getMeasurement("counts", 2);
|
||||
let m = this.getMeasurement("counts", 3);
|
||||
|
||||
if (m.SOURCES.indexOf(source) == -1) {
|
||||
throw new Error("Unknown source for search: " + source);
|
||||
}
|
||||
|
||||
let id = m.interestingEngines[engine] || "other";
|
||||
let field = id + "." + source;
|
||||
return this.enqueueStorageOperation(function recordSearch() {
|
||||
return m.incrementDailyCounter(field);
|
||||
});
|
||||
let field = m.getEngineID(engine) + "." + source;
|
||||
if (this.storage.hasFieldFromMeasurement(m.id, field,
|
||||
this.storage.FIELD_DAILY_COUNTER)) {
|
||||
let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
|
||||
return this.enqueueStorageOperation(function recordSearchKnownField() {
|
||||
return this.storage.incrementDailyCounterFromFieldID(fieldID);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
// Otherwise, we first need to create the field.
|
||||
return this.enqueueStorageOperation(function recordFieldAndSearch() {
|
||||
// This function has to return a promise.
|
||||
return Task.spawn(function () {
|
||||
let fieldID = yield this.storage.registerField(m.id, field,
|
||||
this.storage.FIELD_DAILY_COUNTER);
|
||||
yield this.storage.incrementDailyCounterFromFieldID(fieldID);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -17,13 +17,10 @@ const DEFAULT_ENGINES = [
|
|||
];
|
||||
|
||||
function MockSearchCountMeasurement() {
|
||||
bsp.SearchCountMeasurement2.call(this);
|
||||
bsp.SearchCountMeasurement3.call(this);
|
||||
}
|
||||
MockSearchCountMeasurement.prototype = {
|
||||
__proto__: bsp.SearchCountMeasurement2.prototype,
|
||||
getDefaultEngines: function () {
|
||||
return DEFAULT_ENGINES;
|
||||
},
|
||||
__proto__: bsp.SearchCountMeasurement3.prototype,
|
||||
};
|
||||
|
||||
function MockSearchesProvider() {
|
||||
|
@ -52,24 +49,29 @@ add_task(function test_record() {
|
|||
|
||||
let now = new Date();
|
||||
|
||||
for (let engine of DEFAULT_ENGINES) {
|
||||
yield provider.recordSearch(engine.name, "abouthome");
|
||||
yield provider.recordSearch(engine.name, "contextmenu");
|
||||
yield provider.recordSearch(engine.name, "searchbar");
|
||||
yield provider.recordSearch(engine.name, "urlbar");
|
||||
// Record searches for all but one of our defaults, and one engine that's
|
||||
// not a default.
|
||||
for (let engine of DEFAULT_ENGINES.concat([{name: "Not Default", identifier: "notdef"}])) {
|
||||
if (engine.identifier == "yahoo") {
|
||||
continue;
|
||||
}
|
||||
yield provider.recordSearch(engine, "abouthome");
|
||||
yield provider.recordSearch(engine, "contextmenu");
|
||||
yield provider.recordSearch(engine, "searchbar");
|
||||
yield provider.recordSearch(engine, "urlbar");
|
||||
}
|
||||
|
||||
// Invalid sources should throw.
|
||||
let errored = false;
|
||||
try {
|
||||
yield provider.recordSearch(DEFAULT_ENGINES[0].name, "bad source");
|
||||
yield provider.recordSearch(DEFAULT_ENGINES[0], "bad source");
|
||||
} catch (ex) {
|
||||
errored = true;
|
||||
} finally {
|
||||
do_check_true(errored);
|
||||
}
|
||||
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
let data = yield m.getValues();
|
||||
do_check_eq(data.days.size, 1);
|
||||
do_check_true(data.days.hasDay(now));
|
||||
|
@ -77,17 +79,27 @@ add_task(function test_record() {
|
|||
let day = data.days.getDay(now);
|
||||
for (let engine of DEFAULT_ENGINES) {
|
||||
let identifier = engine.identifier;
|
||||
if (identifier == "foobar") {
|
||||
identifier = "other";
|
||||
}
|
||||
let expected = identifier != "yahoo";
|
||||
|
||||
for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
|
||||
let field = identifier + "." + source;
|
||||
do_check_true(day.has(field));
|
||||
do_check_eq(day.get(field), 1);
|
||||
if (expected) {
|
||||
do_check_true(day.has(field));
|
||||
do_check_eq(day.get(field), 1);
|
||||
} else {
|
||||
do_check_false(day.has(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also, check that our non-default engine contributed, with a computed
|
||||
// identifier.
|
||||
let identifier = "notdef";
|
||||
for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
|
||||
let field = identifier + "." + source;
|
||||
do_check_true(day.has(field));
|
||||
}
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
@ -96,7 +108,7 @@ add_task(function test_includes_other_fields() {
|
|||
let provider = new MockSearchesProvider();
|
||||
|
||||
yield provider.init(storage);
|
||||
let m = provider.getMeasurement("counts", 2);
|
||||
let m = provider.getMeasurement("counts", 3);
|
||||
|
||||
// Register a search against a provider that isn't live in this session.
|
||||
let id = yield m.storage.registerField(m.id, "test.searchbar",
|
||||
|
|
|
@ -583,10 +583,15 @@ BrowserTabActor.prototype = {
|
|||
dbg_assert(this.actorID,
|
||||
"tab should have an actorID.");
|
||||
|
||||
let windowUtils = this.window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let response = {
|
||||
actor: this.actorID,
|
||||
title: this.title,
|
||||
url: this.url
|
||||
url: this.url,
|
||||
outerWindowID: windowUtils.outerWindowID
|
||||
};
|
||||
|
||||
// Walk over tab actors added by extensions and add them to a new ActorPool.
|
||||
|
|
|
@ -30,7 +30,15 @@ function run_test() {
|
|||
}
|
||||
do_check_eq(gUpdateManager.activeUpdate.state, STATE_DOWNLOADING);
|
||||
|
||||
do_test_finished();
|
||||
// Pause the download and reload the Update Manager with an empty update so
|
||||
// the Application Update Service doesn't write the update xml files during
|
||||
// xpcom-shutdown which will leave behind the test directory.
|
||||
gAUS.pauseDownload();
|
||||
writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true);
|
||||
writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
|
||||
reloadUpdateManagerData();
|
||||
|
||||
do_timeout(TEST_CHECK_TIMEOUT, do_test_finished);
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
|
|
|
@ -440,6 +440,16 @@ MetroInput::InitTouchEventTouchList(WidgetTouchEvent* aEvent)
|
|||
static_cast<void*>(&aEvent->touches));
|
||||
}
|
||||
|
||||
bool
|
||||
MetroInput::ShouldDeliverInputToRecognizer()
|
||||
{
|
||||
// If the event is destined for chrome deliver all events to the recognizer.
|
||||
if (mChromeHitTestCacheForTouch) {
|
||||
return true;
|
||||
}
|
||||
return mRecognizerWantsEvents;
|
||||
}
|
||||
|
||||
// This event is raised when the user pushes the left mouse button, presses a
|
||||
// pen to the surface, or presses a touch screen.
|
||||
HRESULT
|
||||
|
@ -483,44 +493,26 @@ MetroInput::OnPointerPressed(UI::Core::ICoreWindow* aSender,
|
|||
// If this is the first touchstart of a touch session reset some
|
||||
// tracking flags and dispatch the event with a custom callback
|
||||
// so we can check preventDefault result.
|
||||
mTouchStartDefaultPrevented = false;
|
||||
mTouchMoveDefaultPrevented = false;
|
||||
mContentConsumingTouch = false;
|
||||
mRecognizerWantsEvents = true;
|
||||
mIsFirstTouchMove = true;
|
||||
mCancelable = true;
|
||||
mTouchCancelSent = false;
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEventWithCallback(touchEvent, &MetroInput::OnPointerPressedCallback);
|
||||
} else {
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEventIgnoreStatus(touchEvent);
|
||||
mCanceledIds.Clear();
|
||||
}
|
||||
|
||||
if (!mTouchStartDefaultPrevented) {
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEvent(touchEvent);
|
||||
|
||||
if (ShouldDeliverInputToRecognizer()) {
|
||||
mGestureRecognizer->ProcessDownEvent(currentPoint.Get());
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MetroInput::OnPointerPressedCallback()
|
||||
{
|
||||
nsEventStatus status = DeliverNextQueuedTouchEvent();
|
||||
mTouchStartDefaultPrevented = (nsEventStatus_eConsumeNoDefault == status);
|
||||
if (mTouchStartDefaultPrevented) {
|
||||
// If content canceled the first touchstart don't generate any gesture based
|
||||
// input - clear the recognizer state without sending any events.
|
||||
mGestureRecognizer->CompleteGesture();
|
||||
// Let the apz know content wants to consume touch events.
|
||||
mWidget->ApzContentConsumingTouch();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MetroInput::AddPointerMoveDataToRecognizer(UI::Core::IPointerEventArgs* aArgs)
|
||||
{
|
||||
// Only feed move input to the recognizer if the first touchstart and
|
||||
// subsequent touchmove return results were not eConsumeNoDefault.
|
||||
if (!mTouchStartDefaultPrevented && !mTouchMoveDefaultPrevented) {
|
||||
if (ShouldDeliverInputToRecognizer()) {
|
||||
WRL::ComPtr<Foundation::Collections::IVector<UI::Input::PointerPoint*>>
|
||||
pointerPoints;
|
||||
aArgs->GetIntermediatePoints(pointerPoints.GetAddressOf());
|
||||
|
@ -586,7 +578,7 @@ MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender,
|
|||
WidgetTouchEvent* touchEvent =
|
||||
new WidgetTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get());
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEventIgnoreStatus(touchEvent);
|
||||
DispatchAsyncTouchEvent(touchEvent);
|
||||
}
|
||||
|
||||
touch = CreateDOMTouch(currentPoint.Get());
|
||||
|
@ -597,13 +589,10 @@ MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender,
|
|||
WidgetTouchEvent* touchEvent =
|
||||
new WidgetTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get());
|
||||
|
||||
// If this is the first touch move of our session, we should check the result.
|
||||
// Note we may lose some touch move data here for the recognizer since we want
|
||||
// to wait until we have the result of the first touchmove dispatch. For gesture
|
||||
// based events this shouldn't break anything.
|
||||
// If this is the first touch move of our session, dispatch it now.
|
||||
if (mIsFirstTouchMove) {
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEventWithCallback(touchEvent, &MetroInput::OnFirstPointerMoveCallback);
|
||||
DispatchAsyncTouchEvent(touchEvent);
|
||||
mIsFirstTouchMove = false;
|
||||
}
|
||||
|
||||
|
@ -612,22 +601,6 @@ MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender,
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MetroInput::OnFirstPointerMoveCallback()
|
||||
{
|
||||
nsEventStatus status = DeliverNextQueuedTouchEvent();
|
||||
mCancelable = false;
|
||||
mTouchMoveDefaultPrevented = (nsEventStatus_eConsumeNoDefault == status);
|
||||
// Let the apz know whether content wants to consume touch events
|
||||
if (mTouchMoveDefaultPrevented) {
|
||||
mWidget->ApzContentConsumingTouch();
|
||||
// reset the recognizer
|
||||
mGestureRecognizer->CompleteGesture();
|
||||
} else if (!mTouchMoveDefaultPrevented && !mTouchStartDefaultPrevented) {
|
||||
mWidget->ApzContentIgnoringTouch();
|
||||
}
|
||||
}
|
||||
|
||||
// This event is raised when the user lifts the left mouse button, lifts a
|
||||
// pen from the surface, or lifts her/his finger from a touch screen.
|
||||
HRESULT
|
||||
|
@ -667,7 +640,7 @@ MetroInput::OnPointerReleased(UI::Core::ICoreWindow* aSender,
|
|||
WidgetTouchEvent* touchEvent =
|
||||
new WidgetTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get());
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
DispatchAsyncTouchEventIgnoreStatus(touchEvent);
|
||||
DispatchAsyncTouchEvent(touchEvent);
|
||||
}
|
||||
|
||||
// Remove this touch point from our map. Eventually all touch points are
|
||||
|
@ -679,11 +652,9 @@ MetroInput::OnPointerReleased(UI::Core::ICoreWindow* aSender,
|
|||
WidgetTouchEvent* touchEvent =
|
||||
new WidgetTouchEvent(true, NS_TOUCH_END, mWidget.Get());
|
||||
touchEvent->touches.AppendElement(CreateDOMTouch(currentPoint.Get()));
|
||||
DispatchAsyncTouchEventIgnoreStatus(touchEvent);
|
||||
DispatchAsyncTouchEvent(touchEvent);
|
||||
|
||||
// If content didn't cancel the first touchstart feed touchend data to the
|
||||
// recognizer.
|
||||
if (!mTouchStartDefaultPrevented && !mTouchMoveDefaultPrevented) {
|
||||
if (ShouldDeliverInputToRecognizer()) {
|
||||
mGestureRecognizer->ProcessUpEvent(currentPoint.Get());
|
||||
}
|
||||
|
||||
|
@ -1179,7 +1150,7 @@ MetroInput::DeliverNextQueuedEventIgnoreStatus()
|
|||
}
|
||||
|
||||
void
|
||||
MetroInput::DispatchAsyncTouchEventIgnoreStatus(WidgetTouchEvent* aEvent)
|
||||
MetroInput::DispatchAsyncTouchEvent(WidgetTouchEvent* aEvent)
|
||||
{
|
||||
aEvent->time = ::GetMessageTime();
|
||||
mModifierKeyState.Update();
|
||||
|
@ -1190,7 +1161,7 @@ MetroInput::DispatchAsyncTouchEventIgnoreStatus(WidgetTouchEvent* aEvent)
|
|||
NS_DispatchToCurrentThread(runnable);
|
||||
}
|
||||
|
||||
nsEventStatus
|
||||
void
|
||||
MetroInput::DeliverNextQueuedTouchEvent()
|
||||
{
|
||||
nsEventStatus status;
|
||||
|
@ -1203,88 +1174,118 @@ MetroInput::DeliverNextQueuedTouchEvent()
|
|||
/*
|
||||
* We go through states here and make different decisions in each:
|
||||
*
|
||||
* 1) delivering first touchpoint touchstart or its first touchmove
|
||||
* Our callers (OnFirstPointerMoveCallback, OnPointerPressedCallback) will
|
||||
* check our result and set mTouchStartDefaultPrevented or
|
||||
* mTouchMoveDefaultPrevented appropriately. Deliver touch events to the apz
|
||||
* (ignoring return result) and to content and return the content event
|
||||
* status result to our caller.
|
||||
* 2) mTouchStartDefaultPrevented or mTouchMoveDefaultPrevented are true
|
||||
* Deliver touch to content after transforming through the apz. Our callers
|
||||
* handle calling cancel for the touch sequence on the apz.
|
||||
* 3) mTouchStartDefaultPrevented and mTouchMoveDefaultPrevented are false
|
||||
* Deliver events to the apz. If the apz returns eConsumeNoDefault dispatch
|
||||
* a touchcancel to content and do not deliver any additional events there.
|
||||
* (If the apz is doing something with the events we can save ourselves
|
||||
* the overhead of delivering dom events.)
|
||||
* 1) Hit test chrome on first touchstart
|
||||
* If chrome is the target simplify event delivery from that point
|
||||
* on by directing all input to chrome, bypassing the apz.
|
||||
* 2) Process first touchpoint touchstart and touchmove
|
||||
* Check the result and set mContentConsumingTouch appropriately. Deliver
|
||||
* touch events to the apz (ignoring return result) and to content.
|
||||
* 3) If mContentConsumingTouch is true: deliver touch to content after
|
||||
* transforming through the apz. Also let the apz know content is
|
||||
* consuming touch.
|
||||
* 4) If mContentConsumingTouch is false: send a touchcancel to content
|
||||
* and deliver all events to the apz. If the apz is doing something with
|
||||
* the events we can save ourselves the overhead of delivering dom events.
|
||||
*
|
||||
* Notes:
|
||||
* - never rely on the contents of mTouches here, since this is a delayed
|
||||
* callback. mTouches will likely have been modified.
|
||||
*/
|
||||
|
||||
// Test for chrome vs. content target. To do this we only use the first touch
|
||||
// point since that will be the input batch target. Cache this for touch events
|
||||
// since HitTestChrome has to send a dom event.
|
||||
if (event->message == NS_TOUCH_START) {
|
||||
if (mCancelable && event->message == NS_TOUCH_START) {
|
||||
nsRefPtr<Touch> touch = event->touches[0];
|
||||
LayoutDeviceIntPoint pt = LayoutDeviceIntPoint::FromUntyped(touch->mRefPoint);
|
||||
bool apzIntersect = mWidget->HitTestAPZC(mozilla::ScreenPoint(pt.x, pt.y));
|
||||
mChromeHitTestCacheForTouch = (apzIntersect && HitTestChrome(pt));
|
||||
}
|
||||
|
||||
// Check if content called preventDefault on touchstart or first touchmove. If so
|
||||
// and the event is destined for chrome, send the event. If destined for content,
|
||||
// translate coordinates through the apz then send.
|
||||
if (mTouchStartDefaultPrevented || mTouchMoveDefaultPrevented) {
|
||||
if (!mChromeHitTestCacheForTouch) {
|
||||
// ContentReceivedTouch has already been called so this shouldn't cause
|
||||
// the apz to react. We still need to transform our coordinates though.
|
||||
mWidget->ApzReceiveInputEvent(event);
|
||||
}
|
||||
// If this event is destined for chrome, deliver it directly there bypassing
|
||||
// the apz.
|
||||
if (!mCancelable && mChromeHitTestCacheForTouch) {
|
||||
mWidget->DispatchEvent(event, status);
|
||||
return status;
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward event data to apz. If the apz consumes the event, don't forward to
|
||||
// content if this is not a cancelable event.
|
||||
WidgetTouchEvent transformedEvent(*event);
|
||||
status = mWidget->ApzReceiveInputEvent(event, &transformedEvent);
|
||||
if (!mCancelable && status == nsEventStatus_eConsumeNoDefault) {
|
||||
if (!mTouchCancelSent) {
|
||||
mTouchCancelSent = true;
|
||||
DispatchTouchCancel();
|
||||
// If we have yet to deliver the first touch start and touch move, deliver the
|
||||
// event to both content and the apz. Ignore the apz's return result since we
|
||||
// give content the option of saying it wants to consume touch for both events.
|
||||
if (mCancelable) {
|
||||
WidgetTouchEvent transformedEvent(*event);
|
||||
mWidget->ApzReceiveInputEvent(event, &transformedEvent);
|
||||
mWidget->DispatchEvent(mChromeHitTestCacheForTouch ? event : &transformedEvent, status);
|
||||
if (event->message == NS_TOUCH_START) {
|
||||
mContentConsumingTouch = (nsEventStatus_eConsumeNoDefault == status);
|
||||
// Disable gesture based events (taps, swipes, rotation) if
|
||||
// preventDefault is called on touchstart.
|
||||
mRecognizerWantsEvents = !(nsEventStatus_eConsumeNoDefault == status);
|
||||
} else if (event->message == NS_TOUCH_MOVE) {
|
||||
mCancelable = false;
|
||||
// Add this result to to our content comsuming flag
|
||||
if (!mContentConsumingTouch) {
|
||||
mContentConsumingTouch = (nsEventStatus_eConsumeNoDefault == status);
|
||||
}
|
||||
// Let the apz know if content wants to consume touch events, or cancel
|
||||
// the touch block for content.
|
||||
if (mContentConsumingTouch) {
|
||||
mWidget->ApzContentConsumingTouch();
|
||||
} else {
|
||||
mWidget->ApzContentIgnoringTouch();
|
||||
DispatchTouchCancel(&transformedEvent);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
// If content is consuming touch don't generate any gesture based
|
||||
// input - clear the recognizer state without sending any events.
|
||||
if (!ShouldDeliverInputToRecognizer()) {
|
||||
mGestureRecognizer->CompleteGesture();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Deliver event. If this is destined for chrome, use the untransformed event
|
||||
// data, if it's destined for content, use the transformed event.
|
||||
mWidget->DispatchEvent(!mChromeHitTestCacheForTouch ? &transformedEvent : event, status);
|
||||
return status;
|
||||
// If content called preventDefault on touchstart or first touchmove send
|
||||
// the event to content.
|
||||
if (mContentConsumingTouch) {
|
||||
// ContentReceivedTouch has already been called in the mCancelable block
|
||||
// above so this shouldn't cause the apz to react. We still need to
|
||||
// transform our coordinates though.
|
||||
mWidget->ApzReceiveInputEvent(event);
|
||||
mWidget->DispatchEvent(event, status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward event data to apz.
|
||||
mWidget->ApzReceiveInputEvent(event);
|
||||
}
|
||||
|
||||
void
|
||||
MetroInput::DispatchTouchCancel()
|
||||
MetroInput::DispatchTouchCancel(WidgetTouchEvent* aEvent)
|
||||
{
|
||||
LogFunction();
|
||||
// From the spec: The touch point or points that were removed must be
|
||||
// included in the changedTouches attribute of the TouchEvent, and must
|
||||
// not be included in the touches and targetTouches attributes.
|
||||
// (We are 'removing' all touch points that have been sent to content
|
||||
// thus far.)
|
||||
MOZ_ASSERT(aEvent);
|
||||
// Send a touchcancel for each pointer id we have a corresponding start
|
||||
// for. Note we can't rely on mTouches here since touchends remove points
|
||||
// from it. The only time we end up in here is if the apz is consuming
|
||||
// events, so this array shouldn't be very large.
|
||||
WidgetTouchEvent touchEvent(true, NS_TOUCH_CANCEL, mWidget.Get());
|
||||
InitTouchEventTouchList(&touchEvent);
|
||||
mWidget->DispatchEvent(&touchEvent, sThrowawayStatus);
|
||||
}
|
||||
nsTArray< nsRefPtr<dom::Touch> >& touches = aEvent->touches;
|
||||
for (uint32_t i = 0; i < touches.Length(); ++i) {
|
||||
dom::Touch* touch = touches[i];
|
||||
if (!touch) {
|
||||
continue;
|
||||
}
|
||||
int32_t id = touch->Identifier();
|
||||
if (mCanceledIds.Contains(id)) {
|
||||
continue;
|
||||
}
|
||||
mCanceledIds.AppendElement(id);
|
||||
touchEvent.touches.AppendElement(touch);
|
||||
}
|
||||
if (!touchEvent.touches.Length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
MetroInput::DispatchAsyncTouchEventWithCallback(WidgetTouchEvent* aEvent,
|
||||
void (MetroInput::*Callback)())
|
||||
{
|
||||
aEvent->time = ::GetMessageTime();
|
||||
mModifierKeyState.Update();
|
||||
mModifierKeyState.InitInputEvent(*aEvent);
|
||||
mInputEventQueue.Push(aEvent);
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, Callback);
|
||||
NS_DispatchToCurrentThread(runnable);
|
||||
mWidget->DispatchEvent(&touchEvent, sThrowawayStatus);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -183,6 +183,7 @@ private:
|
|||
uint32_t aMagEventType,
|
||||
uint32_t aRotEventType);
|
||||
uint16_t ProcessInputTypeForGesture(IEdgeGestureEventArgs* aArgs);
|
||||
bool ShouldDeliverInputToRecognizer();
|
||||
|
||||
// The W3C spec states that "whether preventDefault has been called" should
|
||||
// be tracked on a per-touchpoint basis, but it also states that touchstart
|
||||
|
@ -207,11 +208,11 @@ private:
|
|||
// events will be generated based on the touchstart and touchend events.
|
||||
// For example, a set of mousemove, mousedown, and mouseup events might
|
||||
// be sent if a tap is detected.
|
||||
bool mTouchStartDefaultPrevented;
|
||||
bool mTouchMoveDefaultPrevented;
|
||||
bool mContentConsumingTouch;
|
||||
bool mIsFirstTouchMove;
|
||||
bool mCancelable;
|
||||
bool mTouchCancelSent;
|
||||
bool mRecognizerWantsEvents;
|
||||
nsTArray<uint32_t> mCanceledIds;
|
||||
|
||||
// In the old Win32 way of doing things, we would receive a WM_TOUCH event
|
||||
// that told us the state of every touchpoint on the touch surface. If
|
||||
|
@ -273,21 +274,15 @@ private:
|
|||
|
||||
// Async event dispatching
|
||||
void DispatchAsyncEventIgnoreStatus(WidgetInputEvent* aEvent);
|
||||
void DispatchAsyncTouchEventIgnoreStatus(WidgetTouchEvent* aEvent);
|
||||
void DispatchAsyncTouchEventWithCallback(WidgetTouchEvent* aEvent,
|
||||
void (MetroInput::*Callback)());
|
||||
void DispatchAsyncTouchEvent(WidgetTouchEvent* aEvent);
|
||||
|
||||
// Async event callbacks
|
||||
void DeliverNextQueuedEventIgnoreStatus();
|
||||
nsEventStatus DeliverNextQueuedTouchEvent();
|
||||
|
||||
// Misc. specialty async callbacks
|
||||
void OnPointerPressedCallback();
|
||||
void OnFirstPointerMoveCallback();
|
||||
void DeliverNextQueuedTouchEvent();
|
||||
|
||||
// Sync event dispatching
|
||||
void DispatchEventIgnoreStatus(WidgetGUIEvent* aEvent);
|
||||
void DispatchTouchCancel();
|
||||
void DispatchTouchCancel(WidgetTouchEvent* aEvent);
|
||||
|
||||
nsDeque mInputEventQueue;
|
||||
static nsEventStatus sThrowawayStatus;
|
||||
|
|
Загрузка…
Ссылка в новой задаче