merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-07-22 15:53:08 +02:00
commit 0a354359a4
46 changed files with 781 additions and 508 deletions

View File

@ -6,6 +6,8 @@
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
DIRS += ["source/modules/system"]
JS_MODULES_PATH = 'modules/sdk'
EXTRA_JS_MODULES += [

View File

@ -7,178 +7,7 @@ module.metadata = {
"stability": "experimental"
};
var { Cc, Ci } = require("chrome");
var { Cu } = require("chrome");
var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
var ID = exports.ID = appInfo.ID;
var name = exports.name = appInfo.name;
var version = exports.version = appInfo.version;
var platformVersion = exports.platformVersion = appInfo.platformVersion;
// The following mapping of application names to GUIDs was taken from:
//
// https://addons.mozilla.org/en-US/firefox/pages/appversions
//
// Using the GUID instead of the app's name is preferable because sometimes
// re-branded versions of a product have different names: for instance,
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
// GUID.
// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
// in sync, so if you change one, change the other too!
var ids = exports.ids = {
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
};
function is(name) {
if (!(name in ids))
throw new Error("Unkown Mozilla Application: " + name);
return ID == ids[name];
};
exports.is = is;
function isOneOf(names) {
for (var i = 0; i < names.length; i++)
if (is(names[i]))
return true;
return false;
};
exports.isOneOf = isOneOf;
/**
* Use this to check whether the given version (e.g. xulApp.platformVersion)
* is in the given range. Versions must be in version comparator-compatible
* format. See MDC for details:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
*/
var versionInRange = exports.versionInRange =
function versionInRange(version, lowInclusive, highExclusive) {
return (vc.compare(version, lowInclusive) >= 0) &&
(vc.compare(version, highExclusive) < 0);
}
const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
const reSubInfinity = /\.[*x]/g;
const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
const reRangeSeparator = /\s*\|\|\s*/;
const compares = {
"=": function (c) { return c === 0 },
">=": function (c) { return c >= 0 },
"<=": function (c) { return c <= 0},
"<": function (c) { return c < 0 },
">": function (c) { return c > 0 }
}
function normalizeRange(range) {
return range
.replace(reOnlyInifinity, "")
.replace(reSubInfinity, ".*")
.replace(reHyphenRange, ">=$1 <=$2")
}
/**
* Compare the versions given, using the comparison operator provided.
* Internal use only.
*
* @example
* compareVersion("1.2", "<=", "1.*") // true
*
* @param {String} version
* A version to compare
*
* @param {String} comparison
* The comparison operator
*
* @param {String} compareVersion
* A version to compare
*/
function compareVersion(version, comparison, compareVersion) {
let hasWildcard = compareVersion.indexOf("*") !== -1;
comparison = comparison || "=";
if (hasWildcard) {
switch (comparison) {
case "=":
let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
return versionInRange(version, zeroVersion, compareVersion);
case ">=":
compareVersion = compareVersion.replace(reSubInfinity, ".0");
break;
}
}
let compare = compares[comparison];
return typeof compare === "function" && compare(vc.compare(version, compareVersion));
}
/**
* Returns `true` if `version` satisfies the `versionRange` given.
* If only an argument is passed, is used as `versionRange` and compared against
* `xulApp.platformVersion`.
*
* `versionRange` is either a string which has one or more space-separated
* descriptors, or a range like "fromVersion - toVersion".
* Version range descriptors may be any of the following styles:
*
* - "version" Must match `version` exactly
* - "=version" Same as just `version`
* - ">version" Must be greater than `version`
* - ">=version" Must be greater or equal than `version`
* - "<version" Must be less than `version`
* - "<=version" Must be less or equal than `version`
* - "1.2.x" or "1.2.*" See 'X version ranges' below
* - "*" or "" (just an empty string) Matches any version
* - "version1 - version2" Same as ">=version1 <=version2"
* - "range1 || range2" Passes if either `range1` or `range2` are satisfied
*
* For example, these are all valid:
* - "1.0.0 - 2.9999.9999"
* - ">=1.0.2 <2.1.2"
* - ">1.0.2 <=2.3.4"
* - "2.0.1"
* - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
* - "2.x" (equivalent to "2.*")
* - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
*/
function satisfiesVersion(version, versionRange) {
if (arguments.length === 1) {
versionRange = version;
version = appInfo.version;
}
let ranges = versionRange.trim().split(reRangeSeparator);
return ranges.some(function(range) {
range = normalizeRange(range);
// No versions' range specified means that any version satisfies the
// requirements.
if (range === "")
return true;
let matches = range.match(reVersionRange);
if (!matches)
return false;
let [, lowMod, lowVer, highMod, highVer] = matches;
return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
? compareVersion(version, highMod, highVer)
: true);
});
}
exports.satisfiesVersion = satisfiesVersion;
Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);

View File

@ -0,0 +1,185 @@
/* 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";
var EXPORTED_SYMBOLS = ["XulApp"];
var { classes: Cc, interfaces: Ci } = Components;
var exports = {};
var XulApp = exports;
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
var ID = exports.ID = appInfo.ID;
var name = exports.name = appInfo.name;
var version = exports.version = appInfo.version;
var platformVersion = exports.platformVersion = appInfo.platformVersion;
// The following mapping of application names to GUIDs was taken from:
//
// https://addons.mozilla.org/en-US/firefox/pages/appversions
//
// Using the GUID instead of the app's name is preferable because sometimes
// re-branded versions of a product have different names: for instance,
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
// GUID.
// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
// in sync, so if you change one, change the other too!
var ids = exports.ids = {
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
};
function is(name) {
if (!(name in ids))
throw new Error("Unkown Mozilla Application: " + name);
return ID == ids[name];
};
exports.is = is;
function isOneOf(names) {
for (var i = 0; i < names.length; i++)
if (is(names[i]))
return true;
return false;
};
exports.isOneOf = isOneOf;
/**
* Use this to check whether the given version (e.g. xulApp.platformVersion)
* is in the given range. Versions must be in version comparator-compatible
* format. See MDC for details:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
*/
var versionInRange = exports.versionInRange =
function versionInRange(version, lowInclusive, highExclusive) {
return (vc.compare(version, lowInclusive) >= 0) &&
(vc.compare(version, highExclusive) < 0);
}
const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
const reSubInfinity = /\.[*x]/g;
const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
const reRangeSeparator = /\s*\|\|\s*/;
const compares = {
"=": function (c) { return c === 0 },
">=": function (c) { return c >= 0 },
"<=": function (c) { return c <= 0},
"<": function (c) { return c < 0 },
">": function (c) { return c > 0 }
}
function normalizeRange(range) {
return range
.replace(reOnlyInifinity, "")
.replace(reSubInfinity, ".*")
.replace(reHyphenRange, ">=$1 <=$2")
}
/**
* Compare the versions given, using the comparison operator provided.
* Internal use only.
*
* @example
* compareVersion("1.2", "<=", "1.*") // true
*
* @param {String} version
* A version to compare
*
* @param {String} comparison
* The comparison operator
*
* @param {String} compareVersion
* A version to compare
*/
function compareVersion(version, comparison, compareVersion) {
let hasWildcard = compareVersion.indexOf("*") !== -1;
comparison = comparison || "=";
if (hasWildcard) {
switch (comparison) {
case "=":
let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
return versionInRange(version, zeroVersion, compareVersion);
case ">=":
compareVersion = compareVersion.replace(reSubInfinity, ".0");
break;
}
}
let compare = compares[comparison];
return typeof compare === "function" && compare(vc.compare(version, compareVersion));
}
/**
* Returns `true` if `version` satisfies the `versionRange` given.
* If only an argument is passed, is used as `versionRange` and compared against
* `xulApp.platformVersion`.
*
* `versionRange` is either a string which has one or more space-separated
* descriptors, or a range like "fromVersion - toVersion".
* Version range descriptors may be any of the following styles:
*
* - "version" Must match `version` exactly
* - "=version" Same as just `version`
* - ">version" Must be greater than `version`
* - ">=version" Must be greater or equal than `version`
* - "<version" Must be less than `version`
* - "<=version" Must be less or equal than `version`
* - "1.2.x" or "1.2.*" See 'X version ranges' below
* - "*" or "" (just an empty string) Matches any version
* - "version1 - version2" Same as ">=version1 <=version2"
* - "range1 || range2" Passes if either `range1` or `range2` are satisfied
*
* For example, these are all valid:
* - "1.0.0 - 2.9999.9999"
* - ">=1.0.2 <2.1.2"
* - ">1.0.2 <=2.3.4"
* - "2.0.1"
* - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
* - "2.x" (equivalent to "2.*")
* - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
*/
function satisfiesVersion(version, versionRange) {
if (arguments.length === 1) {
versionRange = version;
version = appInfo.version;
}
let ranges = versionRange.trim().split(reRangeSeparator);
return ranges.some(function(range) {
range = normalizeRange(range);
// No versions' range specified means that any version satisfies the
// requirements.
if (range === "")
return true;
let matches = range.match(reVersionRange);
if (!matches)
return false;
let [, lowMod, lowVer, highMod, highVer] = matches;
return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
? compareVersion(version, highMod, highVer)
: true);
});
}
exports.satisfiesVersion = satisfiesVersion;

View File

@ -0,0 +1,11 @@
# -*- 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/.
JS_MODULES_PATH = 'modules/sdk/system'
EXTRA_JS_MODULES += [
'XulApp.js',
]

View File

@ -17,15 +17,7 @@
title="&fontsDialog.title;"
dlgbuttons="accept,cancel,help"
ondialoghelp="openPrefsHelp()"
#ifdef XP_UNIX
#ifdef XP_MACOSX
style="width: &window.macWidth; !important;">
#else
style="width: &window.unixWidth; !important;">
#endif
#else
style="width: &window.width; !important;">
#endif
style="">
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>

View File

@ -153,8 +153,7 @@ var gContentPane = {
*/
configureFonts: function ()
{
openDialog("chrome://browser/content/preferences/fonts.xul",
"Browser:FontPreferences", null);
gSubDialog.open("chrome://browser/content/preferences/fonts.xul");
},
/**
@ -182,8 +181,7 @@ var gContentPane = {
*/
showTranslationExceptions: function ()
{
openDialog("chrome://browser/content/preferences/translation.xul",
"Browser:TranslationExceptions", null);
gSubDialog.open("chrome://browser/content/preferences/translation.xul");
},
openTranslationProviderAttribution: function ()

View File

@ -9,7 +9,7 @@ function test() {
waitForExplicitFinish();
// open a new window and setup the window state.
newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", "about:blank");
whenWindowLoaded(newWin, function () {
let newState = {
windows: [{

View File

@ -29,6 +29,6 @@ function test() {
function newWindow(callback) {
let opts = "chrome,all,dialog=no,height=800,width=800";
let win = window.openDialog(getBrowserURL(), "_blank", opts);
let win = window.openDialog(getBrowserURL(), "_blank", opts, "about:blank");
whenDelayedStartupFinished(win, () => callback(win));
}

View File

@ -105,7 +105,7 @@ function newWindowWithTabView(shownCallback, loadCallback, width, height) {
let winHeight = height || 800;
let win = window.openDialog(getBrowserURL(), "_blank",
"chrome,all,dialog=no,height=" + winHeight +
",width=" + winWidth);
",width=" + winWidth, "about:blank");
whenWindowLoaded(win, function () {
if (loadCallback)
@ -333,7 +333,7 @@ function newWindowWithState(state, callback) {
.getService(Ci.nsISessionStore);
let opts = "chrome,all,dialog=no,height=800,width=800";
let win = window.openDialog(getBrowserURL(), "_blank", opts);
let win = window.openDialog(getBrowserURL(), "_blank", opts, "about:blank");
let numConditions = 2;
let check = function () {
@ -433,3 +433,19 @@ function waitForOnBeforeUnloadDialog(browser, callback) {
});
}, true);
}
/**
* Overrides browser.js' OpenBrowserWindow() function to enforce an initial
* tab different from about:home to not hit the network.
*/
function OpenBrowserWindow(aOptions) {
let features = "";
let url = "about:blank";
if (aOptions && aOptions.private || false) {
features = ",private";
url = "about:privatebrowsing";
}
return openDialog(getBrowserURL(), "", "chrome,all,dialog=no" + features, url);
}

View File

@ -3643,9 +3643,14 @@ VariablesView.stringifiers.byObjectKind = {
switch (preview.nodeType) {
case Ci.nsIDOMNode.DOCUMENT_NODE: {
let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
{ onlyCropQuery: !concise });
return aGrip.class + " \u2192 " + location;
let result = aGrip.class;
if (preview.location) {
let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
{ onlyCropQuery: !concise });
result += " \u2192 " + location;
}
return result;
}
case Ci.nsIDOMNode.ATTRIBUTE_NODE: {

View File

@ -250,6 +250,7 @@ run-if = os == "mac"
[browser_webconsole_for_of.js]
[browser_webconsole_history.js]
[browser_webconsole_input_field_focus_on_panel_select.js]
[browser_webconsole_inspect-parsed-documents.js]
[browser_webconsole_js_input_expansion.js]
[browser_webconsole_jsterm.js]
[browser_webconsole_live_filtering_of_message_types.js]

View File

@ -0,0 +1,33 @@
/* 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 that dynamically created (HTML|XML|SVG)Documents can be inspected by
// clicking on the object in console (bug 1035198).
const TEST_CASES = [
{
input: '(new DOMParser()).parseFromString("<a />", "text/html")',
output: "HTMLDocument",
inspectable: true,
},
{
input: '(new DOMParser()).parseFromString("<a />", "application/xml")',
output: "XMLDocument",
inspectable: true,
},
{
input: '(new DOMParser()).parseFromString("<svg></svg>", "image/svg+xml")',
output: "SVGDocument",
inspectable: true,
},
];
const TEST_URI = "data:text/html;charset=utf8," +
"browser_webconsole_inspect-parsed-documents.js";
let test = asyncTest(function* () {
let {tab} = yield loadTab(TEST_URI);
let hud = yield openConsole(tab);
yield checkOutputForInputs(hud, TEST_CASES);
});

View File

@ -69,9 +69,11 @@ let inputTests = [
// 8
{
input: "new String('hello world')",
output: '"hello world"',
input: "new String('hello')",
output: 'String [ "h", "e", "l", "l", "o" ]',
printOutput: "hello",
inspectable: true,
variablesViewLabel: "String[5]"
},
];

View File

@ -44,6 +44,13 @@ SimpleTest.registerCleanupFunction(() => {
gDevTools.testing = false;
});
/**
* Define an async test based on a generator function
*/
function asyncTest(generator) {
return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finishTest);
}
function log(aMsg)
{
dump("*** WebConsoleTest: " + aMsg + "\n");

View File

@ -533,7 +533,10 @@ exports.AppManager = AppManager = {
_updateUSBRuntimes: function() {
this.runtimeList.usb = [];
for (let id of Devices.available()) {
this.runtimeList.usb.push(new USBRuntime(id));
let r = new USBRuntime(id);
this.runtimeList.usb.push(r);
r.updateNameFromADB().then(
() => this.update("runtimelist"), () => {});
}
this.update("runtimelist");
},

View File

@ -33,7 +33,24 @@ USBRuntime.prototype = {
return this.id;
},
getName: function() {
return this.id;
return this._productModel || this.id;
},
updateNameFromADB: function() {
if (this._productModel) {
return promise.resolve();
}
let device = Devices.getByName(this.id);
let deferred = promise.defer();
if (device && device.shell) {
device.shell("getprop ro.product.model").then(stdout => {
this._productModel = stdout;
deferred.resolve();
}, () => {});
} else {
this._productModel = null;
deferred.reject();
}
return deferred.promise;
},
}

View File

@ -3,9 +3,6 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY fontsDialog.title "Fonts">
<!ENTITY window.width "39em">
<!ENTITY window.macWidth "43em">
<!ENTITY window.unixWidth "41em">
<!ENTITY language.label "Fonts for:">
<!ENTITY language.accesskey "F">

View File

@ -126,6 +126,7 @@ tab[selected] {
/* buttons and menulists */
button,
colorpicker[type="button"],
menulist {
-moz-appearance: none;
height: 30px;
@ -142,20 +143,28 @@ menulist {
}
button:not([disabled="true"]):hover,
colorpicker[type="button"]:not([disabled="true"]):hover,
menulist:not([disabled="true"]):hover {
background-color: #EBEBEB;
}
button:not([disabled="true"]):hover:active,
colorpicker[type="button"]:not([disabled="true"]):hover:active,
menulist[open="true"]:not([disabled="true"]) {
background-color: #DADADA;
}
button[disabled="true"],
colorpicker[type="button"][disabled="true"],
menulist[disabled="true"] {
opacity: 0.5;
}
colorpicker[type="button"] {
padding: 6px;
width: 50px;
}
button > .button-box,
menulist > .menulist-label-box {
padding-right: 10px !important;
@ -893,6 +902,11 @@ tree:not(#rejectsTree) {
min-height: 15em;
}
:-moz-any(dialog, window, prefwindow) groupbox {
-moz-margin-start: 8px;
-moz-margin-end: 8px;
}
/**
* End sub-dialog
*/

View File

@ -112,9 +112,10 @@ public class SearchHistoryProvider extends SharedBrowserDatabaseProvider {
String[] selectionArgs, String sortOrder) {
String groupBy = null;
String having = null;
return getReadableDatabase(uri).query(SearchHistory.TABLE_NAME, projection,
selection, selectionArgs,
groupBy, having, sortOrder);
final Cursor cursor = getReadableDatabase(uri).query(SearchHistory.TABLE_NAME, projection,
selection, selectionArgs, groupBy, having, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override

View File

@ -22,4 +22,7 @@ public class Constants {
public static final String YAHOO_WEB_SEARCH_BASE_URL = "https://search.yahoo.com/search?p=";
public static final String YAHOO_WEB_SEARCH_RESULTS_FILTER = "//search.yahoo.com";
public static final String INTENT_START_SEARCH = "org.mozilla.search.intent.START_SEARCH";
public static final String INTENT_START_SEARCH_QUERY_EXTRA = "org.mozilla.search.intent.START_SEARCH_QUERY_EXTRA";
}

View File

@ -4,10 +4,14 @@
package org.mozilla.search;
import android.content.AsyncQueryHandler;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.search.autocomplete.AcceptsSearchQuery;
/**
@ -27,18 +31,20 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
}
private State state = State.START;
private AsyncQueryHandler queryHandler;
@Override
protected void onCreate(Bundle stateBundle) {
super.onCreate(stateBundle);
setContentView(R.layout.search_activity_main);
queryHandler = new AsyncQueryHandler(getContentResolver()) {};
}
@Override
public void onSearch(String s) {
startPostsearch();
((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch))
.startSearch(s);
protected void onDestroy() {
super.onDestroy();
queryHandler = null;
}
@Override
@ -48,6 +54,14 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
startPresearch();
}
@Override
public void onSearch(String query) {
startPostsearch();
storeQuery(query);
((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch))
.setUrl("https://search.yahoo.com/search?p=" + Uri.encode(query));
}
private void startPresearch() {
if (state != State.PRESEARCH) {
state = State.PRESEARCH;
@ -72,4 +86,15 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
super.onBackPressed();
}
}
/**
* Store the search query in Fennec's search history database.
*/
private void storeQuery(String query) {
final ContentValues cv = new ContentValues();
cv.put(SearchHistory.QUERY, query);
// Setting 0 for the token, since we only have one type of insert.
// Setting null for the cookie, since we don't handle the result of the insert.
queryHandler.startInsert(0, null, SearchHistory.CONTENT_URI, cv);
}
}

View File

@ -21,24 +21,24 @@ public class PostSearchFragment extends Fragment {
private static final String LOGTAG = "PostSearchFragment";
private WebView webview;
private static String HIDE_BANNER_SCRIPT = "javascript:(function(){var tag=document.createElement('style');" +
private static final String HIDE_BANNER_SCRIPT = "javascript:(function(){var tag=document.createElement('style');" +
"tag.type='text/css';document.getElementsByTagName('head')[0].appendChild(tag);tag.innerText='#nav,#header{display:none}'})();";
public PostSearchFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View mainView = inflater.inflate(R.layout.search_activity_detail, container, false);
webview = (WebView) inflater.inflate(R.layout.search_fragment_post_search, container, false);
webview = (WebView) mainView.findViewById(R.id.webview);
webview.setWebViewClient(new LinkInterceptingClient());
webview.setWebChromeClient(new StyleInjectingClient());
// This is required for our greasemonkey terror script.
webview.getSettings().setJavaScriptEnabled(true);
return mainView;
return webview;
}
/**

View File

@ -4,55 +4,122 @@
package org.mozilla.search;
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import org.mozilla.search.stream.PreloadAgent;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.search.autocomplete.AcceptsSearchQuery;
/**
* 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.
* This fragment is responsible for managing the card stream.
*/
public class PreSearchFragment extends ListFragment {
public class PreSearchFragment extends Fragment {
private ArrayAdapter<PreloadAgent.TmpItem> adapter;
private AcceptsSearchQuery searchListener;
private SimpleCursorAdapter cursorAdapter;
private ListView listView;
private final String[] PROJECTION = new String[]{SearchHistory.QUERY, SearchHistory._ID};
private static final int LOADER_ID_SEARCH_HISTORY = 1;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PreSearchFragment() {
// Mandatory empty constructor for Android's Fragment.
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setDivider(null);
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof AcceptsSearchQuery) {
searchListener = (AcceptsSearchQuery) activity;
} else {
throw new ClassCastException(activity.toString() + " must implement AcceptsSearchQuery.");
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
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;
public void onDetach() {
super.onDetach();
searchListener = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID_SEARCH_HISTORY, null, new SearchHistoryLoaderCallbacks());
cursorAdapter = new SimpleCursorAdapter(getActivity(), R.layout.search_card_history, null,
PROJECTION, new int[]{R.id.site_name}, 0);
}
@Override
public void onDestroy() {
super.onDestroy();
getLoaderManager().destroyLoader(LOADER_ID_SEARCH_HISTORY);
cursorAdapter.swapCursor(null);
cursorAdapter = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
listView = (ListView) inflater.inflate(R.layout.search_fragment_pre_search, container, false);
listView.setAdapter(cursorAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor c = cursorAdapter.getCursor();
if (c == null || !c.moveToPosition(position)) {
return;
}
};
final String query = c.getString(c.getColumnIndexOrThrow(SearchHistory.QUERY));
if (!TextUtils.isEmpty(query)) {
searchListener.onSearch(query);
}
}
});
return listView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
listView.setAdapter(null);
listView = null;
}
private class SearchHistoryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), SearchHistory.CONTENT_URI,
PROJECTION, null, null, SearchHistory.DATE_LAST_VISITED + " DESC");
}
setListAdapter(adapter);
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (cursorAdapter != null) {
cursorAdapter.swapCursor(data);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (cursorAdapter != null) {
cursorAdapter.swapCursor(null);
}
}
}
}

View File

@ -5,19 +5,27 @@
package org.mozilla.search.autocomplete;
import android.content.Context;
import android.text.SpannableString;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import org.mozilla.search.R;
import org.mozilla.search.autocomplete.SearchFragment.Suggestion;
import java.util.List;
/**
* The adapter that is used to populate the autocomplete rows.
*/
class AutoCompleteAdapter extends ArrayAdapter<String> {
class AutoCompleteAdapter extends ArrayAdapter<Suggestion> {
private final AcceptsJumpTaps acceptsJumpTaps;
private final LayoutInflater inflater;
public AutoCompleteAdapter(Context context, AcceptsJumpTaps acceptsJumpTaps) {
// Uses '0' for the template id since we are overriding getView
// and supplying our own view.
@ -26,22 +34,30 @@ class AutoCompleteAdapter extends ArrayAdapter<String> {
// Disable notifying on change. We will notify ourselves in update.
setNotifyOnChange(false);
inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AutoCompleteRowView view;
if (convertView == null) {
view = new AutoCompleteRowView(getContext());
} else {
view = (AutoCompleteRowView) convertView;
convertView = inflater.inflate(R.layout.search_auto_complete_row, null);
}
view.setOnJumpListener(acceptsJumpTaps);
view.setMainText(getItem(position));
final Suggestion suggestion = getItem(position);
return view;
final TextView textView = (TextView) convertView.findViewById(R.id.auto_complete_row_text);
textView.setText(suggestion.display);
final View jumpButton = convertView.findViewById(R.id.auto_complete_row_jump_button);
jumpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptsJumpTaps.onJumpTap(suggestion.value);
}
});
return convertView;
}
/**
@ -49,10 +65,10 @@ class AutoCompleteAdapter extends ArrayAdapter<String> {
*
* @param suggestions List of search suggestions.
*/
public void update(List<String> suggestions) {
public void update(List<Suggestion> suggestions) {
clear();
if (suggestions != null) {
for (String s : suggestions) {
for (Suggestion s : suggestions) {
add(s);
}
}

View File

@ -1,61 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.search.autocomplete;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.mozilla.search.R;
/**
* One row withing the autocomplete suggestion list.
*/
class AutoCompleteRowView extends LinearLayout {
private TextView textView;
private AcceptsJumpTaps onJumpListener;
public AutoCompleteRowView(Context context) {
super(context);
init();
}
private void init() {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.search_auto_complete_row, this, true);
textView = (TextView) findViewById(R.id.auto_complete_row_text);
findViewById(R.id.auto_complete_row_jump_button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null == onJumpListener) {
Log.e("SuggestionRow.onJump", "jump listener is null");
return;
}
onJumpListener.onJumpTap(textView.getText().toString());
}
});
}
public void setMainText(String s) {
textView.setText(s);
}
public void setOnJumpListener(AcceptsJumpTaps onJumpListener) {
this.onJumpListener = onJumpListener;
}
}

View File

@ -13,7 +13,9 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -28,6 +30,7 @@ import android.widget.TextView;
import org.mozilla.search.R;
import java.util.ArrayList;
import java.util.List;
/**
@ -48,6 +51,9 @@ public class SearchFragment extends Fragment
// Maximum number of results returned by the suggestion client
private static final int SUGGESTION_MAX = 5;
// Color of search term match in search suggestion
private static final int SUGGESTION_HIGHLIGHT_COLOR = 0xFF999999;
private SuggestClient suggestClient;
private SuggestionLoaderCallbacks suggestionLoaderCallbacks;
@ -155,8 +161,8 @@ public class SearchFragment extends Fragment
suggestionDropdown.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final String query = (String) suggestionDropdown.getItemAtPosition(position);
startSearch(query);
final Suggestion suggestion = (Suggestion) suggestionDropdown.getItemAtPosition(position);
startSearch(suggestion.value);
}
});
@ -252,32 +258,48 @@ public class SearchFragment extends Fragment
editText.setSelection(suggestion.length());
}
private class SuggestionLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<String>> {
@Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
// suggestClient is set to null in onDestroyView(), so using it
// safely here relies on the fact that onCreateLoader() is called
// synchronously in restartLoader().
return new SuggestionAsyncLoader(getActivity(), suggestClient, args.getString(KEY_SEARCH_TERM));
}
public static class Suggestion {
@Override
public void onLoadFinished(Loader<List<String>> loader, List<String> suggestions) {
autoCompleteAdapter.update(suggestions);
}
private static final ForegroundColorSpan COLOR_SPAN =
new ForegroundColorSpan(SUGGESTION_HIGHLIGHT_COLOR);
@Override
public void onLoaderReset(Loader<List<String>> loader) {
if (autoCompleteAdapter != null) {
autoCompleteAdapter.update(null);
public final String value;
public final SpannableString display;
public Suggestion(String value, String searchTerm) {
this.value = value;
display = new SpannableString(value);
// Highlight mixed-case matches.
final int start = value.toLowerCase().indexOf(searchTerm.toLowerCase());
if (start >= 0) {
display.setSpan(COLOR_SPAN, start, searchTerm.length(), 0);
}
}
}
private static class SuggestionAsyncLoader extends AsyncTaskLoader<List<String>> {
private class SuggestionLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<Suggestion>> {
@Override
public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
return new SuggestionAsyncLoader(getActivity(), suggestClient, args.getString(KEY_SEARCH_TERM));
}
@Override
public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> suggestions) {
autoCompleteAdapter.update(suggestions);
}
@Override
public void onLoaderReset(Loader<List<Suggestion>> loader) {
autoCompleteAdapter.update(null);
}
}
private static class SuggestionAsyncLoader extends AsyncTaskLoader<List<Suggestion>> {
private final SuggestClient suggestClient;
private final String searchTerm;
private List<String> suggestions;
private List<Suggestion> suggestions;
public SuggestionAsyncLoader(Context context, SuggestClient suggestClient, String searchTerm) {
super(context);
@ -287,12 +309,19 @@ public class SearchFragment extends Fragment
}
@Override
public List<String> loadInBackground() {
return suggestClient.query(searchTerm);
public List<Suggestion> loadInBackground() {
final List<String> values = suggestClient.query(searchTerm);
final List<Suggestion> result = new ArrayList<Suggestion>(values.size());
for (String value : values) {
result.add(new Suggestion(value, searchTerm));
}
return result;
}
@Override
public void deliverResult(List<String> suggestions) {
public void deliverResult(List<Suggestion> suggestions) {
this.suggestions = suggestions;
if (isStarted()) {

View File

@ -1,48 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.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 PreSearchFragment
* and the set of all agents. See autocomplete.AutoCompleteFragmentManager.
*/
public class PreloadAgent {
public static final List<TmpItem> ITEMS = new ArrayList<TmpItem>();
private static final Map<String, TmpItem> ITEM_MAP = new HashMap<String, TmpItem>();
static {
addItem(new TmpItem("1", "Pre-load item1"));
addItem(new TmpItem("2", "Pre-load item2"));
}
private static void addItem(TmpItem item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
public static class TmpItem {
public final String id;
public final String content;
public TmpItem(String id, String content) {
this.id = id;
this.content = content;
}
@Override
public String toString() {
return content;
}
}
}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,16 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<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"
tools:context="org.mozilla.search.PostSearchFragment">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webview"/>
</RelativeLayout>

View File

@ -11,25 +11,22 @@
tools:context=".MainActivity">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.mozilla.search.PostSearchFragment"
android:layout_marginTop="@dimen/search_bar_height"
android:id="@+id/postsearch"
/>
<fragment
android:name="org.mozilla.search.PostSearchFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.mozilla.search.PreSearchFragment"
android:layout_marginTop="@dimen/search_bar_height"
android:layout_marginTop="@dimen/search_bar_height"/>
<fragment
android:id="@+id/presearch"
/>
<fragment
android:name="org.mozilla.search.PreSearchFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.mozilla.search.autocomplete.SearchFragment"
android:id="@+id/search_fragment"/>
android:layout_marginTop="@dimen/search_bar_height"/>
<fragment
android:id="@+id/search_fragment"
android:name="org.mozilla.search.autocomplete.SearchFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>

View File

@ -1,18 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@drawable/search_card_background"
android:orientation="vertical"
>
<TextView
android:id="@+id/card_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

View File

@ -0,0 +1,17 @@
<!-- 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/. -->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/site_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:padding="25dp"
android:background="@drawable/search_card_background"
android:fontFamily="sans-serif-thin"
android:textSize="16sp"/>

View File

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

View File

@ -0,0 +1,10 @@
<!-- 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/. -->
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"
android:dividerHeight="0dp"/>

View File

@ -12,4 +12,6 @@
<color name="card_background">#ffffff</color>
<color name="card_shadow_1">#d4d4d4</color>
<color name="card_shadow_2">#dddddd</color>
<!-- Search suggestion highlight color is defined in SearchFragment.java -->
</resources>

View File

@ -8,12 +8,10 @@ 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/AutoCompleteRowView.java',
'java/org/mozilla/search/autocomplete/SearchFragment.java',
'java/org/mozilla/search/autocomplete/SuggestClient.java',
'java/org/mozilla/search/Constants.java',
'java/org/mozilla/search/MainActivity.java',
'java/org/mozilla/search/PostSearchFragment.java',
'java/org/mozilla/search/PreSearchFragment.java',
'java/org/mozilla/search/stream/PreloadAgent.java',
]

View File

@ -1757,14 +1757,17 @@ ThreadClient.prototype = {
this.client.request(packet, (aResponse) => {
// Ignoring errors, since the user may be setting a breakpoint in a
// dead script that will reappear on a page reload.
if (aOnResponse) {
let bpClient;
if (aResponse.actor) {
let root = this.client.mainRoot;
let bpClient = new BreakpointClient(
bpClient = new BreakpointClient(
this.client,
aResponse.actor,
location,
root.traits.conditionalBreakpoints ? condition : undefined
);
}
if (aOnResponse) {
aOnResponse(aResponse, bpClient);
}
if (aCallback) {

View File

@ -98,7 +98,7 @@ BreakpointStore.prototype = {
get size() { return this._size; },
/**
* Add a breakpoint to the breakpoint store.
* Add a breakpoint to the breakpoint store if it doesn't already exist.
*
* @param Object aBreakpoint
* The breakpoint to be added (not copied). It is an object with the
@ -109,10 +109,11 @@ BreakpointStore.prototype = {
* the whole line)
* - condition (optional)
* - actor (optional)
* @returns Object aBreakpoint
* The new or existing breakpoint.
*/
addBreakpoint: function (aBreakpoint) {
let { url, line, column } = aBreakpoint;
let updating = false;
if (column != null) {
if (!this._breakpoints[url]) {
@ -121,16 +122,22 @@ BreakpointStore.prototype = {
if (!this._breakpoints[url][line]) {
this._breakpoints[url][line] = [];
}
this._breakpoints[url][line][column] = aBreakpoint;
if (!this._breakpoints[url][line][column]) {
this._breakpoints[url][line][column] = aBreakpoint;
this._size++;
}
return this._breakpoints[url][line][column];
} else {
// Add a breakpoint that breaks on the whole line.
if (!this._wholeLineBreakpoints[url]) {
this._wholeLineBreakpoints[url] = [];
}
this._wholeLineBreakpoints[url][line] = aBreakpoint;
if (!this._wholeLineBreakpoints[url][line]) {
this._wholeLineBreakpoints[url][line] = aBreakpoint;
this._size++;
}
return this._wholeLineBreakpoints[url][line];
}
this._size++;
},
/**
@ -1439,9 +1446,7 @@ ThreadActor.prototype = {
let locationPromise = this.sources.getGeneratedLocation(aRequest.location);
return locationPromise.then(({url, line, column}) => {
if (line == null ||
line < 0 ||
this.dbg.findScripts({ url: url }).length == 0) {
if (line == null || line < 0) {
return {
error: "noScript",
message: "Requested setting a breakpoint on "
@ -1537,12 +1542,11 @@ ThreadActor.prototype = {
// Find all scripts matching the given location
let scripts = this.dbg.findScripts(aLocation);
if (scripts.length == 0) {
// Since we did not find any scripts to set the breakpoint on now, return
// early. When a new script that matches this breakpoint location is
// introduced, the breakpoint actor will already be in the breakpoint store
// and will be set at that time.
return {
error: "noScript",
message: "Requested setting a breakpoint on "
+ aLocation.url + ":" + aLocation.line
+ (aLocation.column != null ? ":" + aLocation.column : "")
+ " but there is no Debugger.Script at that location",
actor: actor.actorID
};
}
@ -3561,16 +3565,29 @@ exports.ObjectActor = ObjectActor;
DebuggerServer.ObjectActorPreviewers = {
String: [function({obj, threadActor}, aGrip) {
let result = genericObjectPreviewer("String", String, obj, threadActor);
if (result) {
let length = DevToolsUtils.getProperty(obj, "length");
if (typeof length != "number") {
return false;
}
let length = DevToolsUtils.getProperty(obj, "length");
aGrip.displayString = result.value;
if (!result || typeof length != "number") {
return false;
}
aGrip.preview = {
kind: "ArrayLike",
length: length
};
if (threadActor._gripDepth > 1) {
return true;
}
let items = aGrip.preview.items = [];
const max = Math.min(result.value.length, OBJECT_PREVIEW_MAX_ITEMS);
for (let i = 0; i < max; i++) {
let value = threadActor.createValueGrip(result.value[i]);
items.push(value);
}
return true;
}],

View File

@ -510,6 +510,17 @@ function resume(threadClient) {
return rdpRequest(threadClient, threadClient.resume);
}
/**
* Interrupt JS execution for the specified thread.
*
* @param ThreadClient threadClient
* @returns Promise
*/
function interrupt(threadClient) {
dumpn("Interrupting.");
return rdpRequest(threadClient, threadClient.interrupt);
}
/**
* Resume JS execution for the specified thread and then wait for the next pause
* event.

View File

@ -8,62 +8,65 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_same_breakpoint();
testSameBreakpoint();
});
});
do_test_pending();
}
function test_same_breakpoint()
{
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
let path = getFilePath('test_breakpoint-01.js');
let location = {
url: path,
line: gDebuggee.line0 + 3
};
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
gThreadClient.setBreakpoint(location, function (aResponse, secondBpClient) {
do_check_eq(bpClient.actor, secondBpClient.actor,
"Should get the same actor w/ whole line breakpoints");
let location = {
url: path,
line: gDebuggee.line0 + 2,
column: 6
};
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
gThreadClient.setBreakpoint(location, function (aResponse, secondBpClient) {
do_check_eq(bpClient.actor, secondBpClient.actor,
"Should get the same actor column breakpoints");
gClient.close(gCallback);
});
});
});
});
});
const SOURCE_URL = "http://example.com/source.js";
Components.utils.evalInSandbox("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = 1;\n" + // line0 + 2
"var b = 2;\n", // line0 + 3
gDebuggee);
}
const testSameBreakpoint = Task.async(function* () {
yield executeOnNextTickAndWaitForPause(evalCode, gClient);
// Whole line
let wholeLineLocation = {
url: SOURCE_URL,
line: 2
};
let [firstResponse, firstBpClient] = yield setBreakpoint(gThreadClient, wholeLineLocation);
let [secondResponse, secondBpClient] = yield setBreakpoint(gThreadClient, wholeLineLocation);
do_check_eq(firstBpClient.actor, secondBpClient.actor, "Should get the same actor w/ whole line breakpoints");
// Specific column
let columnLocation = {
url: SOURCE_URL,
line: 2,
column: 6
};
let [firstResponse, firstBpClient] = yield setBreakpoint(gThreadClient, columnLocation);
let [secondResponse, secondBpClient] = yield setBreakpoint(gThreadClient, columnLocation);
do_check_eq(secondBpClient.actor, secondBpClient.actor, "Should get the same actor column breakpoints");
finishClient(gClient);
});
function evalCode() {
Components.utils.evalInSandbox(
"" + function doStuff(k) { // line 1
let arg = 15; // line 2 - Step in here
k(arg); // line 3
} + "\n" // line 4
+ "debugger;", // line 5
gDebuggee,
"1.8",
SOURCE_URL,
1
);
}

View File

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that setting a breakpoint in a not-yet-existing script doesn't throw
* an error (see bug 897567). Also make sure that this breakpoint works.
*/
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-breakpoints", aServer);
gDebuggee.console = { log: x => void x };
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient,
"test-breakpoints",
function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
testBreakpoint();
});
});
}
const URL = "test.js";
function setUpCode() {
Cu.evalInSandbox(
"" + function test() { // 1
var a = 1; // 2
debugger; // 3
} + // 4
"\ndebugger;", // 5
gDebuggee,
"1.8",
URL
);
}
const testBreakpoint = Task.async(function* () {
const [response, bpClient] = yield setBreakpoint(gThreadClient, {url: URL, line: 2});
ok(!response.error);
const actor = response.actor;
ok(actor);
yield executeOnNextTickAndWaitForPause(setUpCode, gClient);
yield resume(gThreadClient);
const packet = yield executeOnNextTickAndWaitForPause(gDebuggee.test, gClient);
equal(packet.why.type, "breakpoint")
notEqual(packet.why.actors.indexOf(actor), -1);
finishClient(gClient);
});

View File

@ -16,6 +16,7 @@ function run_test()
test_add_breakpoint();
test_remove_breakpoint();
test_find_breakpoints();
test_duplicate_breakpoints();
}
function test_has_breakpoint() {
@ -165,3 +166,28 @@ function test_find_breakpoints() {
do_check_eq(bpSet.size, 0,
"Should be able to filter the iteration by url and line");
}
function test_duplicate_breakpoints() {
let bpStore = new BreakpointStore();
// Breakpoint with column
let location = {
url: "http://example.com/foo.js",
line: 10,
column: 9
};
bpStore.addBreakpoint(location);
bpStore.addBreakpoint(location);
do_check_eq(bpStore.size, 1, "We should have only 1 column breakpoint");
bpStore.removeBreakpoint(location);
// Breakpoint without column (whole line breakpoint)
location = {
url: "http://example.com/foo.js",
line: 15
};
bpStore.addBreakpoint(location);
bpStore.addBreakpoint(location);
do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint");
bpStore.removeBreakpoint(location);
}

View File

@ -110,6 +110,7 @@ reason = bug 820380
[test_breakpoint-16.js]
[test_breakpoint-17.js]
[test_breakpoint-18.js]
[test_breakpoint-19.js]
[test_conditional_breakpoint-01.js]
[test_conditional_breakpoint-02.js]
[test_conditional_breakpoint-03.js]