зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
5a6021fdf1
|
@ -1048,6 +1048,12 @@
|
|||
</method>
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="popuphidden"><![CDATA[
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
document.getElementById("searchbar").textbox.selectedButton = null;
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
]]></handler>
|
||||
|
||||
<handler event="popupshowing"><![CDATA[
|
||||
// First handle deciding if we are showing the reduced version of the
|
||||
// popup containing only the preferences button. We do this if the
|
||||
|
@ -1236,14 +1242,24 @@
|
|||
|
||||
<handler event="mouseover"><![CDATA[
|
||||
let target = event.originalTarget;
|
||||
if (target.localName == "button" &&
|
||||
target.classList.contains("searchbar-engine-one-off-item") &&
|
||||
!target.classList.contains("dummy")) {
|
||||
let list = document.getAnonymousElementByAttribute(this, "anonid",
|
||||
"search-panel-one-offs")
|
||||
for (let button = list.firstChild; button; button = button.nextSibling)
|
||||
button.removeAttribute("selected");
|
||||
}
|
||||
if (target.localName != "button")
|
||||
return;
|
||||
|
||||
if ((target.classList.contains("searchbar-engine-one-off-item") &&
|
||||
!target.classList.contains("dummy")) ||
|
||||
target.classList.contains("addengine-item") ||
|
||||
target.classList.contains("search-setting-button"))
|
||||
document.getElementById("searchbar").textbox.selectedButton = target;
|
||||
]]></handler>
|
||||
|
||||
<handler event="mouseout"><![CDATA[
|
||||
let target = event.originalTarget;
|
||||
if (target.localName != "button")
|
||||
return;
|
||||
|
||||
let textbox = document.getElementById("searchbar").textbox;
|
||||
if (textbox.selectedButton == target)
|
||||
textbox.selectedButton = null;
|
||||
]]></handler>
|
||||
|
||||
<handler event="click"><![CDATA[
|
||||
|
|
|
@ -509,7 +509,7 @@
|
|||
let type = "unknown";
|
||||
if (aEvent instanceof KeyboardEvent) {
|
||||
type = "key";
|
||||
if (this._textbox.getSelectedOneOff()) {
|
||||
if (this._textbox.selectedButton) {
|
||||
source = "oneoff";
|
||||
}
|
||||
} else if (aEvent instanceof MouseEvent) {
|
||||
|
@ -989,9 +989,15 @@
|
|||
var evt = aEvent || this.mEnterEvent;
|
||||
|
||||
let engine;
|
||||
let oneOff = this.getSelectedOneOff();
|
||||
if (oneOff)
|
||||
let oneOff = this.selectedButton;
|
||||
if (oneOff) {
|
||||
if (!oneOff.engine) {
|
||||
oneOff.doCommand();
|
||||
this.mEnterEvent = null;
|
||||
return;
|
||||
}
|
||||
engine = oneOff.engine;
|
||||
}
|
||||
if (this.mEnterEvent && this._selectionDetails &&
|
||||
this._selectionDetails.currentIndex != -1) {
|
||||
BrowserSearch.searchBar.telemetrySearchDetails = this._selectionDetails;
|
||||
|
@ -1003,30 +1009,116 @@
|
|||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="getSelectedOneOff">
|
||||
<body><![CDATA[
|
||||
let list = document.getAnonymousElementByAttribute(this.popup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
if (!list)
|
||||
return null;
|
||||
<field name="_selectedButton"/>
|
||||
<property name="selectedButton" onget="return this._selectedButton;">
|
||||
<setter><![CDATA[
|
||||
if (this._selectedButton)
|
||||
this._selectedButton.removeAttribute("selected");
|
||||
|
||||
for (let button = list.firstChild; button; button = button.nextSibling) {
|
||||
if (button.hasAttribute("selected"))
|
||||
return button;
|
||||
// Avoid selecting dummy buttons.
|
||||
if (val && !val.classList.contains("dummy")) {
|
||||
val.setAttribute("selected", "true");
|
||||
this._selectedButton = val;
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
this._selectedButton = null;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<method name="getSelectableButtons">
|
||||
<parameter name="aCycleEngines"/>
|
||||
<body><![CDATA[
|
||||
let buttons = [];
|
||||
let oneOff = document.getAnonymousElementByAttribute(this.popup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
|
||||
if (oneOff.classList.contains("dummy"))
|
||||
break;
|
||||
buttons.push(oneOff);
|
||||
}
|
||||
|
||||
if (aCycleEngines)
|
||||
return buttons;
|
||||
|
||||
let addEngine =
|
||||
document.getAnonymousElementByAttribute(this.popup, "anonid", "add-engines");
|
||||
for (addEngine = addEngine.firstChild; addEngine; addEngine = addEngine.nextSibling)
|
||||
buttons.push(addEngine);
|
||||
|
||||
buttons.push(document.getAnonymousElementByAttribute(this.popup, "anonid",
|
||||
"search-settings"));
|
||||
return buttons;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="advanceSelection">
|
||||
<parameter name="aForward"/>
|
||||
<parameter name="aSkipSuggestions"/>
|
||||
<parameter name="aCycleEngines"/>
|
||||
<body><![CDATA[
|
||||
let popup = this.popup;
|
||||
let list = document.getAnonymousElementByAttribute(popup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
let selectedButton = this.selectedButton;
|
||||
let buttons = this.getSelectableButtons(aCycleEngines);
|
||||
|
||||
// If the last suggestion is selected, DOWN selects the first button.
|
||||
if (!aSkipSuggestions && aForward &&
|
||||
popup.selectedIndex + 1 == popup.view.rowCount) {
|
||||
this.selectedButton = buttons[0];
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a one-off is selected and no suggestion is selected (or we skip them)
|
||||
if (selectedButton && (popup.selectedIndex == -1 || aSkipSuggestions)) {
|
||||
// cycle through one-off buttons.
|
||||
let index = buttons.indexOf(selectedButton);
|
||||
if (aForward)
|
||||
++index;
|
||||
else
|
||||
--index;
|
||||
if (index >= 0 && index < buttons.length)
|
||||
this.selectedButton = buttons[index];
|
||||
else
|
||||
this.selectedButton = null;
|
||||
|
||||
if (this.selectedButton || aCycleEngines)
|
||||
return true;
|
||||
|
||||
// Set the selectedIndex to something that will make
|
||||
// handleKeyNavigation (called by autocomplete.xml's onKeyPress
|
||||
// method) reset the text field value to what the user typed.
|
||||
// Doesn't work when aSkipSuggestions=true, see bug 1124747.
|
||||
if (aForward)
|
||||
popup.selectedIndex = popup.view.rowCount - 1;
|
||||
else
|
||||
popup.selectedIndex = popup.view.rowCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!selectedButton) {
|
||||
// If no selection, select the first button or ...
|
||||
if (aForward && aSkipSuggestions) {
|
||||
this.selectedButton = buttons[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!aForward && (aCycleEngines ||
|
||||
(!aSkipSuggestions && popup.selectedIndex == -1))) {
|
||||
// the last button.
|
||||
this.selectedButton = buttons[buttons.length - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="handleKeyboardNavigation">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
// XXXFlorian This method could likely be shortened with a helper
|
||||
// handling moving the selection within the one-off list and
|
||||
// returning a boolean indicating if the event should be stopped.
|
||||
|
||||
let popup = this.popup;
|
||||
if (!popup.popupOpen)
|
||||
return;
|
||||
|
@ -1042,7 +1134,7 @@
|
|||
|
||||
let list = document.getAnonymousElementByAttribute(popup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
if (!list)
|
||||
if (!list) // remove this check when removing the old search UI.
|
||||
return;
|
||||
|
||||
// accel + up/down changes the default engine and shouldn't affect
|
||||
|
@ -1054,129 +1146,26 @@
|
|||
#endif
|
||||
return;
|
||||
|
||||
let selectedButton = this.getSelectedOneOff();
|
||||
let stopEvent = false;
|
||||
|
||||
// Alt + up/down is very similar to (shift +) tab but differs in that
|
||||
// it loops through the list, whereas tab will move the focus out.
|
||||
if (aEvent.altKey &&
|
||||
(aEvent.keyCode == KeyEvent.DOM_VK_DOWN ||
|
||||
aEvent.keyCode == KeyEvent.DOM_VK_UP)) {
|
||||
let forward = aEvent.keyCode == KeyEvent.DOM_VK_DOWN;
|
||||
if (selectedButton) {
|
||||
// cycle though the list of one-off buttons.
|
||||
selectedButton.removeAttribute("selected");
|
||||
if (forward)
|
||||
selectedButton = selectedButton.nextSibling;
|
||||
else
|
||||
selectedButton = selectedButton.previousSibling;
|
||||
|
||||
// Avoid selecting dummy buttons.
|
||||
if (selectedButton && selectedButton.classList.contains("dummy"))
|
||||
selectedButton = null;
|
||||
}
|
||||
else {
|
||||
// If no selection, select the first or last one-off button.
|
||||
if (forward) {
|
||||
selectedButton = list.firstChild;
|
||||
}
|
||||
else {
|
||||
selectedButton = list.lastChild;
|
||||
while (selectedButton.classList.contains("dummy"))
|
||||
selectedButton = selectedButton.previousSibling;
|
||||
}
|
||||
}
|
||||
if (selectedButton)
|
||||
selectedButton.setAttribute("selected", "true");
|
||||
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
return;
|
||||
stopEvent =
|
||||
this.advanceSelection(aEvent.keyCode == KeyEvent.DOM_VK_DOWN,
|
||||
true, true);
|
||||
}
|
||||
else if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN ||
|
||||
aEvent.keyCode == KeyEvent.DOM_VK_UP) {
|
||||
stopEvent = this.advanceSelection(aEvent.keyCode == KeyEvent.DOM_VK_DOWN);
|
||||
}
|
||||
else if (aEvent.keyCode == KeyEvent.DOM_VK_TAB) {
|
||||
stopEvent = this.advanceSelection(!aEvent.shiftKey, true);
|
||||
}
|
||||
|
||||
// If the last suggestion is selected, DOWN selects the first one-off.
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN &&
|
||||
popup.selectedIndex + 1 == popup.view.rowCount) {
|
||||
if (selectedButton)
|
||||
selectedButton.removeAttribute("selected");
|
||||
selectedButton = list.firstChild;
|
||||
if (selectedButton)
|
||||
selectedButton.setAttribute("selected", "true");
|
||||
}
|
||||
|
||||
// If no suggestion is selected and a one-off is selected,
|
||||
// UP and DOWN cycle through one-off buttons.
|
||||
if (popup.selectedIndex == -1 && selectedButton &&
|
||||
(aEvent.keyCode == KeyEvent.DOM_VK_DOWN ||
|
||||
aEvent.keyCode == KeyEvent.DOM_VK_UP)) {
|
||||
selectedButton.removeAttribute("selected");
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN)
|
||||
selectedButton = selectedButton.nextSibling;
|
||||
else
|
||||
selectedButton = selectedButton.previousSibling;
|
||||
if (selectedButton && selectedButton.classList.contains("dummy"))
|
||||
selectedButton = null;
|
||||
|
||||
if (selectedButton) {
|
||||
selectedButton.setAttribute("selected", "true");
|
||||
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
else {
|
||||
// Set the selectedIndex to something that will make
|
||||
// handleKeyNavigation (called by autocomplete.xml's onKeyPress
|
||||
// method) reset the text field value to what the user typed.
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_UP)
|
||||
popup.selectedIndex = popup.view.rowCount;
|
||||
else
|
||||
popup.selectedIndex = popup.view.rowCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing is selected, UP selects the last one-off button.
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_UP &&
|
||||
popup.selectedIndex == -1 && !selectedButton) {
|
||||
selectedButton = list.lastChild;
|
||||
while (selectedButton.classList.contains("dummy"))
|
||||
selectedButton = selectedButton.previousSibling;
|
||||
selectedButton.setAttribute("selected", "true");
|
||||
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_TAB) {
|
||||
if (selectedButton) {
|
||||
// TAB cycles though the list of one-off buttons.
|
||||
selectedButton.removeAttribute("selected");
|
||||
if (aEvent.shiftKey)
|
||||
selectedButton = selectedButton.previousSibling;
|
||||
else
|
||||
selectedButton = selectedButton.nextSibling;
|
||||
|
||||
// Avoid selecting dummy buttons.
|
||||
if (selectedButton && selectedButton.classList.contains("dummy"))
|
||||
selectedButton = null;
|
||||
|
||||
// If we are out of the list, revert the text field to what the user typed.
|
||||
if (!selectedButton) {
|
||||
// Set the selectedIndex to something that will make
|
||||
// handleKeyNavigation (called by autocomplete.xml's onKeyPress
|
||||
// method) reset the text field value to what the user typed.
|
||||
popup.selectedIndex = aEvent.shiftKey ? 0 : popup.view.rowCount - 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If no selection, let the focus escape the panel for shift+<tab>
|
||||
if (aEvent.shiftKey)
|
||||
return;
|
||||
|
||||
// and select the first one-off button for <tab>.
|
||||
selectedButton = list.firstChild;
|
||||
}
|
||||
selectedButton.setAttribute("selected", "true");
|
||||
|
||||
if (stopEvent) {
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
483086-1.xml
|
||||
483086-2.xml
|
||||
head.js
|
||||
opensearch.html
|
||||
test.html
|
||||
testEngine.src
|
||||
testEngine.xml
|
||||
|
@ -41,3 +42,4 @@ skip-if = e10s # Bug ?????? - some issue with progress listeners [JavaScript Err
|
|||
skip-if = e10s || true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir
|
||||
[browser_searchbar_openpopup.js]
|
||||
skip-if = os == "linux" || e10s # Linux has different focus behaviours and e10s seems to have timing issues.
|
||||
[browser_searchbar_keyboard_navigation.js]
|
||||
|
|
|
@ -0,0 +1,431 @@
|
|||
// Tests that keyboard navigation in the search panel works as designed.
|
||||
|
||||
const searchbar = document.getElementById("searchbar");
|
||||
const textbox = searchbar._textbox;
|
||||
const searchPopup = document.getElementById("PopupSearchAutoComplete");
|
||||
|
||||
const kValues = ["foo1", "foo2", "foo3"];
|
||||
const kUserValue = "foo";
|
||||
|
||||
// Get an array of the one-off buttons.
|
||||
function getOneOffs() {
|
||||
let oneOffs = [];
|
||||
let oneOff = document.getAnonymousElementByAttribute(searchPopup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
|
||||
if (oneOff.classList.contains("dummy"))
|
||||
break;
|
||||
oneOffs.push(oneOff);
|
||||
}
|
||||
|
||||
return oneOffs;
|
||||
}
|
||||
|
||||
function getOpenSearchItems() {
|
||||
let os = [];
|
||||
|
||||
let addEngineList =
|
||||
document.getAnonymousElementByAttribute(searchPopup, "anonid",
|
||||
"add-engines");
|
||||
for (let item = addEngineList.firstChild; item; item = item.nextSibling)
|
||||
os.push(item);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
add_task(function* init() {
|
||||
yield promiseNewEngine("testEngine.xml");
|
||||
|
||||
// First cleanup the form history in case other tests left things there.
|
||||
yield new Promise((resolve, reject) => {
|
||||
info("cleanup the search history");
|
||||
searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"},
|
||||
{handleCompletion: resolve,
|
||||
handleError: reject});
|
||||
});
|
||||
|
||||
yield new Promise((resolve, reject) => {
|
||||
info("adding search history values: " + kValues);
|
||||
let ops = kValues.map(value => { return {op: "add",
|
||||
fieldname: "searchbar-history",
|
||||
value: value}
|
||||
});
|
||||
searchbar.FormHistory.update(ops, {
|
||||
handleCompletion: function() {
|
||||
registerCleanupFunction(() => {
|
||||
info("removing search history values: " + kValues);
|
||||
let ops =
|
||||
kValues.map(value => { return {op: "remove",
|
||||
fieldname: "searchbar-history",
|
||||
value: value}
|
||||
});
|
||||
searchbar.FormHistory.update(ops);
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
handleError: reject
|
||||
});
|
||||
});
|
||||
|
||||
textbox.value = kUserValue;
|
||||
registerCleanupFunction(() => { textbox.value = ""; });
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_arrows() {
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
searchbar.focus();
|
||||
yield promise;
|
||||
is(textbox.mController.searchString, kUserValue, "The search string should be 'foo'");
|
||||
|
||||
// Check the initial state of the panel before sending keyboard events.
|
||||
is(searchPopup.view.rowCount, kValues.length, "There should be 3 suggestions");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
|
||||
// The tests will be less meaningful if the first, second, last, and
|
||||
// before-last one-off buttons aren't different. We should always have more
|
||||
// than 4 default engines, but it's safer to check this assumption.
|
||||
let oneOffs = getOneOffs();
|
||||
ok(oneOffs.length >= 4, "we have at least 4 one-off buttons displayed")
|
||||
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// The down arrow should first go through the suggestions.
|
||||
for (let i = 0; i < kValues.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(searchPopup.selectedIndex, i,
|
||||
"the suggestion at index " + i + " should be selected");
|
||||
is(textbox.value, kValues[i],
|
||||
"the textfield value should be " + kValues[i]);
|
||||
}
|
||||
|
||||
// Pressing down again should remove suggestion selection and change the text
|
||||
// field value back to what the user typed, and select the first one-off.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue,
|
||||
"the textfield value should be back to initial value");
|
||||
|
||||
// now cycle through the one-off items, the first one is already selected.
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
}
|
||||
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
// We should now be back to the initial situation.
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
info("now test the up arrow key");
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// cycle through the one-off items, the first one is already selected.
|
||||
for (let i = oneOffs.length; i; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, oneOffs[i - 1],
|
||||
"the one-off button #" + i + " should be selected");
|
||||
}
|
||||
|
||||
// Another press on up should clear the one-off selection and select the
|
||||
// last suggestion.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
for (let i = kValues.length - 1; i >= 0; --i) {
|
||||
is(searchPopup.selectedIndex, i,
|
||||
"the suggestion at index " + i + " should be selected");
|
||||
is(textbox.value, kValues[i],
|
||||
"the textfield value should be " + kValues[i]);
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
}
|
||||
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue,
|
||||
"the textfield value should be back to initial value");
|
||||
});
|
||||
|
||||
add_task(function* test_tab() {
|
||||
is(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar should be focused"); // from the previous test.
|
||||
|
||||
let oneOffs = getOneOffs();
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// Pressing tab should select the first one-off without selecting suggestions.
|
||||
// now cycle through the one-off items, the first one is already selected.
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
}
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue, "the textfield value should be unmodified");
|
||||
|
||||
// One more <tab> selects the settings button.
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// Pressing tab again should close the panel...
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
yield promise;
|
||||
|
||||
// ... and move the focus out of the searchbox.
|
||||
isnot(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar no longer be focused");
|
||||
});
|
||||
|
||||
add_task(function* test_shift_tab() {
|
||||
// First reopen the panel.
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
searchbar.focus();
|
||||
yield promise;
|
||||
|
||||
let oneOffs = getOneOffs();
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// Press up once to select the last button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// Press up again to select the last one-off button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
|
||||
// Pressing shift+tab should cycle through the one-off items.
|
||||
for (let i = oneOffs.length - 1; i >= 0; --i) {
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
if (i)
|
||||
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
|
||||
}
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue, "the textfield value should be unmodified");
|
||||
|
||||
// Pressing shift+tab again should close the panel...
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
|
||||
yield promise;
|
||||
|
||||
// ... and move the focus out of the searchbox.
|
||||
isnot(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar no longer be focused");
|
||||
});
|
||||
|
||||
add_task(function* test_alt_down() {
|
||||
// First refocus the panel.
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
searchbar.focus();
|
||||
yield promise;
|
||||
|
||||
// close the panel using the escape key.
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield promise;
|
||||
|
||||
// check that alt+down opens the panel...
|
||||
promise = promiseEvent(searchPopup, "popupshown");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
yield promise;
|
||||
|
||||
// ... and does nothing else.
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue, "the textfield value should be unmodified");
|
||||
|
||||
// Pressing alt+down should select the first one-off without selecting suggestions
|
||||
// and cycle through the one-off items.
|
||||
let oneOffs = getOneOffs();
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
}
|
||||
|
||||
// One more alt+down keypress and nothing should be selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// another one and the first one-off should be selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should be selected");
|
||||
});
|
||||
|
||||
add_task(function* test_alt_up() {
|
||||
// close the panel using the escape key.
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield promise;
|
||||
|
||||
// check that alt+up opens the panel...
|
||||
promise = promiseEvent(searchPopup, "popupshown");
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
yield promise;
|
||||
|
||||
// ... and does nothing else.
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue, "the textfield value should be unmodified");
|
||||
|
||||
// Pressing alt+up should select the last one-off without selecting suggestions
|
||||
// and cycle up through the one-off items.
|
||||
let oneOffs = getOneOffs();
|
||||
for (let i = oneOffs.length - 1; i >= 0; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
}
|
||||
|
||||
// One more alt+down keypress and nothing should be selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// another one and the last one-off should be selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[oneOffs.length - 1],
|
||||
"the last one-off button should be selected");
|
||||
|
||||
// Cleanup for the next test.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
ok(!textbox.selectedButton, "no one-off should be selected anymore");
|
||||
});
|
||||
|
||||
add_task(function* test_tab_and_arrows() {
|
||||
// Check the initial state is as expected.
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue, "the textfield value should be unmodified");
|
||||
|
||||
// After pressing down, the first sugggestion should be selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(searchPopup.selectedIndex, 0, "first suggestion should be selected");
|
||||
is(textbox.value, kValues[0], "the textfield value should have changed");
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// After pressing tab, the first one-off should be selected,
|
||||
// and the first suggestion still selected.
|
||||
let oneOffs = getOneOffs();
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, 0, "first suggestion should still be selected");
|
||||
|
||||
// After pressing down, the second suggestion should be selected,
|
||||
// and the first one-off still selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should still be selected");
|
||||
is(searchPopup.selectedIndex, 1, "second suggestion should be selected");
|
||||
|
||||
// After pressing up, the first suggestion should be selected again,
|
||||
// and the first one-off still selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should still be selected");
|
||||
is(searchPopup.selectedIndex, 0, "second suggestion should be selected again");
|
||||
|
||||
// After pressing up again, we should have no suggestion selected anymore,
|
||||
// the textfield value back to the user-typed value, and still the first one-off
|
||||
// selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, kUserValue,
|
||||
"the textfield value should be back to user typed value");
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should still be selected");
|
||||
|
||||
// Now pressing down should select the second one-off.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton, oneOffs[1],
|
||||
"the second one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "there should still be no selected suggestion");
|
||||
|
||||
// Finally close the panel.
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
searchPopup.hidePopup();
|
||||
yield promise;
|
||||
});
|
||||
|
||||
add_task(function* test_open_search() {
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
browser.addEventListener("load", function onload() {
|
||||
browser.removeEventListener("load", onload, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
content.location = rootDir + "opensearch.html";
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
searchbar.focus();
|
||||
yield promise;
|
||||
|
||||
let engines = getOpenSearchItems();
|
||||
is(engines.length, 2, "the opensearch.html page exposes 2 engines")
|
||||
|
||||
// Check that there's initially no selection.
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
ok(!textbox.selectedButton, "no button should be selected");
|
||||
|
||||
// Pressing up once selects the setting button...
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// ...and then pressing up selects open search engines.
|
||||
for (let i = engines.length; i; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
let selectedButton = textbox.selectedButton;
|
||||
is(selectedButton, engines[i - 1],
|
||||
"the engine #" + i + " should be selected");
|
||||
ok(selectedButton.classList.contains("addengine-item"),
|
||||
"the button is themed as an engine item");
|
||||
}
|
||||
|
||||
// Pressing up again should select the last one-off button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, getOneOffs().pop(),
|
||||
"the last one-off button should be selected");
|
||||
|
||||
info("now check that the down key navigates open search items as expected");
|
||||
for (let i = 0; i < engines.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton, engines[i],
|
||||
"the engine #" + (i + 1) + " should be selected");
|
||||
}
|
||||
|
||||
// Pressing down on the last engine item selects the settings button.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
searchPopup.hidePopup();
|
||||
yield promise;
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
|
@ -26,7 +26,7 @@ function* synthesizeNativeMouseClick(aElement) {
|
|||
let win = aElement.ownerDocument.defaultView;
|
||||
let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
|
||||
let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
|
||||
|
||||
|
||||
// Wait for the mouseup event to occur before continuing.
|
||||
return new Promise((resolve, reject) => {
|
||||
function eventOccurred(e)
|
||||
|
@ -42,34 +42,6 @@ function* synthesizeNativeMouseClick(aElement) {
|
|||
});
|
||||
}
|
||||
|
||||
function promiseNewEngine(basename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
info("Waiting for engine to be added: " + basename);
|
||||
Services.search.init({
|
||||
onInitComplete: function() {
|
||||
let url = getRootDirectory(gTestPath) + basename;
|
||||
let current = Services.search.currentEngine;
|
||||
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
|
||||
onSuccess: function (engine) {
|
||||
info("Search engine added: " + basename);
|
||||
Services.search.currentEngine = engine;
|
||||
registerCleanupFunction(() => {
|
||||
Services.search.currentEngine = current;
|
||||
Services.search.removeEngine(engine);
|
||||
info("Search engine removed: " + basename);
|
||||
});
|
||||
resolve(engine);
|
||||
},
|
||||
onError: function (errCode) {
|
||||
ok(false, "addEngine failed with error code " + errCode);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* init() {
|
||||
yield promiseNewEngine("testEngine.xml");
|
||||
});
|
||||
|
@ -471,4 +443,3 @@ add_task(function* dont_rollup_oncaretmove() {
|
|||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield promise;
|
||||
});
|
||||
|
||||
|
|
|
@ -136,3 +136,30 @@ function* promiseOnLoad() {
|
|||
});
|
||||
}
|
||||
|
||||
function promiseNewEngine(basename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
info("Waiting for engine to be added: " + basename);
|
||||
Services.search.init({
|
||||
onInitComplete: function() {
|
||||
let url = getRootDirectory(gTestPath) + basename;
|
||||
let current = Services.search.currentEngine;
|
||||
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
|
||||
onSuccess: function (engine) {
|
||||
info("Search engine added: " + basename);
|
||||
Services.search.currentEngine = engine;
|
||||
registerCleanupFunction(() => {
|
||||
Services.search.currentEngine = current;
|
||||
Services.search.removeEngine(engine);
|
||||
info("Search engine removed: " + basename);
|
||||
});
|
||||
resolve(engine);
|
||||
},
|
||||
onError: function (errCode) {
|
||||
ok(false, "addEngine failed with error code " + errCode);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine_mozsearch.xml">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
|
@ -64,6 +64,9 @@ const EVENTS = {
|
|||
// when a recording model is selected
|
||||
RECORDING_SELECTED: "Performance:RecordingSelected",
|
||||
|
||||
// Emitted by the PerformanceView on clear button click
|
||||
UI_CLEAR_RECORDINGS: "Performance:UI:ClearRecordings",
|
||||
|
||||
// Emitted by the PerformanceView on record button click
|
||||
UI_START_RECORDING: "Performance:UI:StartRecording",
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
@ -79,6 +82,9 @@ const EVENTS = {
|
|||
RECORDING_WILL_START: "Performance:RecordingWillStart",
|
||||
RECORDING_WILL_STOP: "Performance:RecordingWillStop",
|
||||
|
||||
// When recordings have been cleared out
|
||||
RECORDINGS_CLEARED: "Performance:RecordingsCleared",
|
||||
|
||||
// When a recording is imported or exported via the PerformanceController
|
||||
RECORDING_IMPORTED: "Performance:RecordingImported",
|
||||
RECORDING_EXPORTED: "Performance:RecordingExported",
|
||||
|
@ -162,6 +168,7 @@ let PerformanceController = {
|
|||
this.stopRecording = this.stopRecording.bind(this);
|
||||
this.importRecording = this.importRecording.bind(this);
|
||||
this.exportRecording = this.exportRecording.bind(this);
|
||||
this.clearRecordings = this.clearRecordings.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
@ -170,6 +177,7 @@ let PerformanceController = {
|
|||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
|
||||
|
@ -188,6 +196,7 @@ let PerformanceController = {
|
|||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
|
||||
|
@ -252,6 +261,22 @@ let PerformanceController = {
|
|||
this.emit(EVENTS.RECORDING_EXPORTED, recording);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Clears all recordings from the list as well as the current recording.
|
||||
* Emits `EVENTS.RECORDINGS_CLEARED` when complete so other components can clean up.
|
||||
*/
|
||||
clearRecordings: Task.async(function* () {
|
||||
let latest = this._getLatestRecording();
|
||||
|
||||
if (latest && latest.isRecording()) {
|
||||
yield this.stopRecording();
|
||||
}
|
||||
|
||||
this._recordings.length = 0;
|
||||
this.setCurrentRecording(null);
|
||||
this.emit(EVENTS.RECORDINGS_CLEARED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Loads a recording from a file, adding it to the recordings list. Emits
|
||||
* `EVENTS.RECORDING_IMPORTED` when the file was loaded.
|
||||
|
|
|
@ -32,20 +32,27 @@ let PerformanceView = {
|
|||
initialize: function () {
|
||||
this._recordButton = $("#record-button");
|
||||
this._importButton = $("#import-button");
|
||||
this._clearButton = $("#clear-button");
|
||||
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onClearButtonClick = this._onClearButtonClick.bind(this);
|
||||
this._lockRecordButton = this._lockRecordButton.bind(this);
|
||||
this._unlockRecordButton = this._unlockRecordButton.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
|
||||
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
|
||||
|
||||
for (let button of $$(".record-button")) {
|
||||
button.addEventListener("click", this._onRecordButtonClick);
|
||||
}
|
||||
this._importButton.addEventListener("click", this._onImportButtonClick);
|
||||
this._clearButton.addEventListener("click", this._onClearButtonClick);
|
||||
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
|
@ -69,6 +76,8 @@ let PerformanceView = {
|
|||
}
|
||||
this._importButton.removeEventListener("click", this._onImportButtonClick);
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
|
@ -119,6 +128,22 @@ let PerformanceView = {
|
|||
this._recordButton.removeAttribute("locked");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a recording is starting, but not yet completed.
|
||||
*/
|
||||
_onRecordingWillStart: function () {
|
||||
this._lockRecordButton();
|
||||
this._recordButton.setAttribute("checked", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a recording is stopping, but not yet completed.
|
||||
*/
|
||||
_onRecordingWillStop: function () {
|
||||
this._lockRecordButton();
|
||||
this._recordButton.removeAttribute("checked");
|
||||
},
|
||||
|
||||
/**
|
||||
* When a recording is complete.
|
||||
*/
|
||||
|
@ -133,17 +158,20 @@ let PerformanceView = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the clear button.
|
||||
*/
|
||||
_onClearButtonClick: function (e) {
|
||||
this.emit(EVENTS.UI_CLEAR_RECORDINGS);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the record button.
|
||||
*/
|
||||
_onRecordButtonClick: function (e) {
|
||||
if (this._recordButton.hasAttribute("checked")) {
|
||||
this._recordButton.removeAttribute("checked");
|
||||
this._lockRecordButton();
|
||||
this.emit(EVENTS.UI_STOP_RECORDING);
|
||||
} else {
|
||||
this._recordButton.setAttribute("checked", "true");
|
||||
this._lockRecordButton();
|
||||
this.emit(EVENTS.UI_START_RECORDING);
|
||||
}
|
||||
},
|
||||
|
@ -166,7 +194,9 @@ let PerformanceView = {
|
|||
* Fired when a recording is selected. Used to toggle the profiler view state.
|
||||
*/
|
||||
_onRecordingSelected: function (_, recording) {
|
||||
if (recording.isRecording()) {
|
||||
if (!recording) {
|
||||
this.setState("empty");
|
||||
} else if (recording.isRecording()) {
|
||||
this.setState("recording");
|
||||
} else {
|
||||
this.setState("recorded");
|
||||
|
|
|
@ -10,6 +10,8 @@ support-files =
|
|||
|
||||
[browser_perf-aaa-run-first-leaktest.js]
|
||||
[browser_perf-allocations-to-samples.js]
|
||||
[browser_perf-clear-01.js]
|
||||
[browser_perf-clear-02.js]
|
||||
[browser_perf-data-massaging-01.js]
|
||||
[browser_perf-data-samples.js]
|
||||
[browser_perf-details-calltree-render.js]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that clearing recordings empties out the recordings list and toggles
|
||||
* the empty notice state.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield PerformanceController.clearRecordings();
|
||||
|
||||
is(RecordingsView.itemCount, 0,
|
||||
"RecordingsView should be empty.");
|
||||
is(PerformanceView.getState(), "empty",
|
||||
"PerformanceView should be in an empty state.");
|
||||
is(PerformanceController.getCurrentRecording(), null,
|
||||
"There should be no current recording.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that clearing recordings empties out the recordings list and stops
|
||||
* a current recording if recording.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let stopped = Promise.all([
|
||||
once(PerformanceController, EVENTS.RECORDING_STOPPED),
|
||||
once(PerformanceController, EVENTS.RECORDINGS_CLEARED)
|
||||
]);
|
||||
PerformanceController.clearRecordings();
|
||||
yield stopped;
|
||||
|
||||
is(RecordingsView.itemCount, 0,
|
||||
"RecordingsView should be empty.");
|
||||
is(PerformanceView.getState(), "empty",
|
||||
"PerformanceView should be in an empty state.");
|
||||
is(PerformanceController.getCurrentRecording(), null,
|
||||
"There should be no current recording.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
|
@ -67,7 +67,7 @@ let DetailsSubview = {
|
|||
* Called when recording stops or is selected.
|
||||
*/
|
||||
_onRecordingStoppedOrSelected: function(_, recording) {
|
||||
if (recording.isRecording()) {
|
||||
if (!recording || recording.isRecording()) {
|
||||
return;
|
||||
}
|
||||
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
|
||||
|
|
|
@ -265,6 +265,9 @@ let OverviewView = {
|
|||
* Called when a new recording is selected.
|
||||
*/
|
||||
_onRecordingSelected: function (_, recording) {
|
||||
if (!recording) {
|
||||
return;
|
||||
}
|
||||
this.markersOverview.dropSelection();
|
||||
this._checkSelection(recording);
|
||||
|
||||
|
|
|
@ -18,12 +18,14 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
|||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingImported = this._onRecordingImported.bind(this);
|
||||
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
|
||||
this._onRecordingsCleared = this._onRecordingsCleared.bind(this);
|
||||
|
||||
this.emptyText = L10N.getStr("noRecordingsText");
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
|
||||
PerformanceController.on(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
|
@ -34,6 +36,7 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
|||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
|
||||
PerformanceController.off(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
|
@ -162,6 +165,13 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
|||
this.finalizeRecording(recordingItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears out all recordings.
|
||||
*/
|
||||
_onRecordingsCleared: function () {
|
||||
this.empty();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds recording data to a recording item in this container.
|
||||
*
|
||||
|
|
|
@ -144,6 +144,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests (exp
|
|||
[browser_console_clear_on_reload.js]
|
||||
[browser_console_click_focus.js]
|
||||
[browser_console_consolejsm_output.js]
|
||||
[browser_console_copy_command.js]
|
||||
[browser_console_dead_objects.js]
|
||||
skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_console_copy_entire_message_context_menu.js]
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
// Tests that the `copy` console helper works as intended.
|
||||
|
||||
let gWebConsole, gJSTerm;
|
||||
|
||||
let TEXT = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
|
||||
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
|
||||
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
|
||||
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
|
||||
"dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
|
||||
"fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
|
||||
"proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +
|
||||
new Date();
|
||||
|
||||
let ID = "select-me";
|
||||
|
||||
add_task(function* init() {
|
||||
yield loadTab("data:text/html;charset=utf-8," +
|
||||
"<body>" +
|
||||
" <div>" +
|
||||
" <h1>Testing copy command</h1>" +
|
||||
" <p>This is some example text</p>" +
|
||||
" <p id='select-me'>"+TEXT+"</p>" +
|
||||
" </div>" +
|
||||
" <div><p></p></div>" +
|
||||
"</body>");
|
||||
|
||||
gWebConsole = yield openConsole();
|
||||
gJSTerm = gWebConsole.jsterm;
|
||||
});
|
||||
|
||||
add_task(function* test_copy() {
|
||||
let RANDOM = Math.random();
|
||||
let string = "Text: " + RANDOM;
|
||||
let obj = {a: 1, b: "foo", c: RANDOM};
|
||||
|
||||
let samples = [[RANDOM, RANDOM],
|
||||
[JSON.stringify(string), string],
|
||||
[obj.toSource(), JSON.stringify(obj, null, " ")],
|
||||
["$('#" + ID + "')", content.document.getElementById(ID).outerHTML]
|
||||
];
|
||||
for (let [source, reference] of samples) {
|
||||
let deferredResult = promise.defer();
|
||||
|
||||
SimpleTest.waitForClipboard(
|
||||
"" + reference,
|
||||
() => {
|
||||
let command = "copy(" + source + ")";
|
||||
info("Attempting to copy: " + source);
|
||||
info("Executing command: " + command);
|
||||
gJSTerm.execute(command, msg => {
|
||||
is(msg, undefined, "Command success: " + command);
|
||||
});
|
||||
},
|
||||
deferredResult.resolve,
|
||||
deferredResult.reject);
|
||||
|
||||
yield deferredResult.promise;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* cleanup() {
|
||||
gWebConsole = gJSTerm = null;
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
finishTest();
|
||||
});
|
|
@ -3282,6 +3282,9 @@ JSTerm.prototype = {
|
|||
case "help":
|
||||
this.hud.owner.openLink(HELP_URL);
|
||||
break;
|
||||
case "copyValueToClipboard":
|
||||
clipboardHelper.copyString(helperResult.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -187,7 +187,6 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
|
|||
background-image: none;
|
||||
}
|
||||
|
||||
.searchbar-engine-one-off-item:hover:not(.dummy),
|
||||
.searchbar-engine-one-off-item[selected] {
|
||||
background-color: Highlight;
|
||||
background-image: none;
|
||||
|
@ -219,7 +218,7 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
|
|||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.addengine-item:hover {
|
||||
.addengine-item[selected] {
|
||||
background-color: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
|
@ -292,7 +291,7 @@ searchbar[oneoffui] .searchbar-engine-button {
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-setting-button:hover {
|
||||
.search-setting-button[selected] {
|
||||
background-color: #d3d3d3;
|
||||
border-top-color: #bdbebe;
|
||||
}
|
||||
|
|
|
@ -788,7 +788,7 @@ public class BrowserApp extends GeckoApp
|
|||
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
|
||||
mBrowserHealthReporter = new BrowserHealthReporter();
|
||||
mReadingListHelper = new ReadingListHelper(appContext);
|
||||
mReadingListHelper = new ReadingListHelper(appContext, getProfile());
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
|
|
|
@ -223,26 +223,33 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
@Override
|
||||
public void onResponse(NativeJSObject nativeJSObject) {
|
||||
final int total = nativeJSObject.optInt("total", 0);
|
||||
final int current = nativeJSObject.optInt("current", 0);
|
||||
updateResult(total, current);
|
||||
if (total == -1) {
|
||||
final int limit = nativeJSObject.optInt("limit", 0);
|
||||
updateResult(Integer.toString(limit) + "+");
|
||||
} else if (total > 0) {
|
||||
final int current = nativeJSObject.optInt("current", 0);
|
||||
updateResult(Integer.toString(current) + "/" + Integer.toString(total));
|
||||
} else {
|
||||
// We display no match-count information, when there were no
|
||||
// matches found, or if matching has been turned off by setting
|
||||
// pref accessibility.typeaheadfind.matchesCountLimit to 0.
|
||||
updateResult("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(NativeJSObject error) {
|
||||
// Gecko didn't respond due to state change, javascript error, etc.
|
||||
updateResult(0, 0);
|
||||
Log.d(LOGTAG, "No response from Gecko on request to match string: [" +
|
||||
searchString + "]");
|
||||
updateResult("");
|
||||
}
|
||||
|
||||
private void updateResult(int total, int current) {
|
||||
final Boolean statusVisibility = (total > 0);
|
||||
final String statusText = current + "/" + total;
|
||||
|
||||
private void updateResult(final String statusText) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mStatusText.setVisibility(statusVisibility ? View.VISIBLE : View.GONE);
|
||||
mStatusText.setVisibility(statusText.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
mStatusText.setText(statusText);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.DBUtils;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
|
@ -18,24 +19,45 @@ import org.mozilla.gecko.util.UIAsyncTask;
|
|||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
public final class ReadingListHelper implements NativeEventListener {
|
||||
private static final String LOGTAG = "ReadingListHelper";
|
||||
private static final String LOGTAG = "GeckoReadingListHelper";
|
||||
|
||||
protected final Context context;
|
||||
private final BrowserDB db;
|
||||
|
||||
public ReadingListHelper(Context context) {
|
||||
private final Uri readingListUriWithProfile;
|
||||
private final ContentObserver contentObserver;
|
||||
|
||||
public ReadingListHelper(Context context, GeckoProfile profile) {
|
||||
this.context = context;
|
||||
this.db = profile.getDB();
|
||||
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
|
||||
"Reader:AddToList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
||||
"Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
||||
|
||||
readingListUriWithProfile = DBUtils.appendProfile(profile.getName(), ReadingListItems.CONTENT_URI);
|
||||
|
||||
contentObserver = new ContentObserver(null) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
fetchContent();
|
||||
}
|
||||
};
|
||||
|
||||
context.getContentResolver().registerContentObserver(readingListUriWithProfile, false, contentObserver);
|
||||
}
|
||||
|
||||
public void uninit() {
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
|
||||
"Reader:AddToList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
||||
"Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
||||
|
||||
context.getContentResolver().unregisterContentObserver(contentObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,6 +68,10 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
handleAddToList(callback, message);
|
||||
break;
|
||||
}
|
||||
case "Reader:UpdateList": {
|
||||
handleUpdateList(message);
|
||||
break;
|
||||
}
|
||||
case "Reader:FaviconRequest": {
|
||||
handleReaderModeFaviconRequest(callback, message.getString("url"));
|
||||
break;
|
||||
|
@ -73,14 +99,8 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
|
||||
// We can't access a NativeJSObject from the background thread, so we need to get the
|
||||
// values here, even if we may not use them to insert an item into the DB.
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ReadingListItems.URL, url);
|
||||
values.put(ReadingListItems.TITLE, message.getString("title"));
|
||||
values.put(ReadingListItems.LENGTH, message.getInt("length"));
|
||||
values.put(ReadingListItems.EXCERPT, message.getString("excerpt"));
|
||||
values.put(ReadingListItems.CONTENT_STATUS, message.getInt("status"));
|
||||
final ContentValues values = getContentValues(message);
|
||||
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -96,12 +116,52 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a reading list item with new meta data.
|
||||
*/
|
||||
private void handleUpdateList(final NativeJSObject message) {
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final ContentValues values = getContentValues(message);
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
db.updateReadingListItem(cr, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates reading list item content values from JS message.
|
||||
*/
|
||||
private ContentValues getContentValues(NativeJSObject message) {
|
||||
final ContentValues values = new ContentValues();
|
||||
if (message.has("id")) {
|
||||
values.put(ReadingListItems._ID, message.getInt("id"));
|
||||
}
|
||||
if (message.has("url")) {
|
||||
values.put(ReadingListItems.URL, message.getString("url"));
|
||||
}
|
||||
if (message.has("title")) {
|
||||
values.put(ReadingListItems.TITLE, message.getString("title"));
|
||||
}
|
||||
if (message.has("length")) {
|
||||
values.put(ReadingListItems.LENGTH, message.getInt("length"));
|
||||
}
|
||||
if (message.has("excerpt")) {
|
||||
values.put(ReadingListItems.EXCERPT, message.getString("excerpt"));
|
||||
}
|
||||
if (message.has("status")) {
|
||||
values.put(ReadingListItems.CONTENT_STATUS, message.getInt("status"));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gecko (ReaderMode) requests the page favicon to append to the
|
||||
* document head for display.
|
||||
*/
|
||||
private void handleReaderModeFaviconRequest(final EventCallback callback, final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground() {
|
||||
|
@ -129,7 +189,6 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
* or by tapping the readinglist-remove icon in the ReaderMode banner.
|
||||
*/
|
||||
private void handleRemoveFromList(final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -145,7 +204,6 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
|
||||
*/
|
||||
private void handleReadingListStatusRequest(final EventCallback callback, final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -176,4 +234,28 @@ public final class ReadingListHelper implements NativeEventListener {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchContent() {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Cursor c = db.getReadingListUnfetched(context.getContentResolver());
|
||||
try {
|
||||
while (c.moveToNext()) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("id", c.getInt(c.getColumnIndexOrThrow(ReadingListItems._ID)));
|
||||
json.put("url", c.getString(c.getColumnIndexOrThrow(ReadingListItems.URL)));
|
||||
GeckoAppShell.sendEventToGecko(
|
||||
GeckoEvent.createBroadcastEvent("Reader:FetchContent", json.toString()));
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Failed to fetch reading list content for item");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,8 +122,10 @@ public interface BrowserDB {
|
|||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getReadingList(ContentResolver cr);
|
||||
public abstract Cursor getReadingListUnfetched(ContentResolver cr);
|
||||
public abstract boolean isReadingListItem(ContentResolver cr, String uri);
|
||||
public abstract void addReadingListItem(ContentResolver cr, ContentValues values);
|
||||
public abstract void updateReadingListItem(ContentResolver cr, ContentValues values);
|
||||
public abstract void removeReadingListItemWithURL(ContentResolver cr, String uri);
|
||||
|
||||
|
||||
|
|
|
@ -774,16 +774,6 @@ public class LocalBrowserDB implements BrowserDB {
|
|||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getReadingList(ContentResolver cr) {
|
||||
return cr.query(mReadingListUriWithProfile,
|
||||
ReadingListItems.DEFAULT_PROJECTION,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
// Returns true if any desktop bookmarks exist, which will be true if the user
|
||||
// has set up sync at one point, or done a profile migration from XUL fennec.
|
||||
private boolean desktopBookmarksExist(ContentResolver cr) {
|
||||
|
@ -983,6 +973,25 @@ public class LocalBrowserDB implements BrowserDB {
|
|||
cr.delete(contentUri, urlEquals, urlArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getReadingList(ContentResolver cr) {
|
||||
return cr.query(mReadingListUriWithProfile,
|
||||
ReadingListItems.DEFAULT_PROJECTION,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getReadingListUnfetched(ContentResolver cr) {
|
||||
return cr.query(mReadingListUriWithProfile,
|
||||
new String[] { ReadingListItems._ID, ReadingListItems.URL },
|
||||
ReadingListItems.CONTENT_STATUS + " = " + ReadingListItems.STATUS_UNFETCHED,
|
||||
null,
|
||||
null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
// Check that required fields are present.
|
||||
|
@ -1009,6 +1018,20 @@ public class LocalBrowserDB implements BrowserDB {
|
|||
debug("Updated " + updated + " rows to new modified time.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
if (!values.containsKey(ReadingListItems._ID)) {
|
||||
throw new IllegalArgumentException("Cannot update reading list item without an ID");
|
||||
}
|
||||
|
||||
final int updated = cr.update(mReadingListUriWithProfile,
|
||||
values,
|
||||
ReadingListItems._ID + " = ? ",
|
||||
new String[] { values.getAsString(ReadingListItems._ID) });
|
||||
|
||||
debug("Updated " + updated + " reading list rows.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
|
||||
|
|
|
@ -170,6 +170,10 @@ public class StubBrowserDB implements BrowserDB {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Cursor getReadingListUnfetched(ContentResolver cr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public boolean isBookmark(ContentResolver cr, String uri) {
|
||||
return false;
|
||||
|
@ -197,6 +201,9 @@ public class StubBrowserDB implements BrowserDB {
|
|||
public void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
}
|
||||
|
||||
public void updateReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
}
|
||||
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Used to create handle movement events for SelectionHandler.
|
||||
const ANCHOR = "ANCHOR";
|
||||
|
@ -60,20 +60,20 @@ function startTests() {
|
|||
*/
|
||||
function testLTR_selectAll() {
|
||||
// Select entire LTR Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
var selection = sh._getSelection();
|
||||
let selection = sh._getSelection();
|
||||
|
||||
var anchorNode = selection.anchorNode;
|
||||
var anchorOffset = selection.anchorOffset;
|
||||
var focusNode = selection.focusNode;
|
||||
var focusOffset = selection.focusOffset;
|
||||
let anchorNode = selection.anchorNode;
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
let focusNode = selection.focusNode;
|
||||
let focusOffset = selection.focusOffset;
|
||||
|
||||
var anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
var focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
let anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
let focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
|
@ -129,20 +129,20 @@ function testLTR_selectAll() {
|
|||
*/
|
||||
function testRTL_selectAll() {
|
||||
// Select entire RTL Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
var selection = sh._getSelection();
|
||||
let selection = sh._getSelection();
|
||||
|
||||
var anchorNode = selection.anchorNode;
|
||||
var anchorOffset = selection.anchorOffset;
|
||||
var focusNode = selection.focusNode;
|
||||
var focusOffset = selection.focusOffset;
|
||||
let anchorNode = selection.anchorNode;
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
let focusNode = selection.focusNode;
|
||||
let focusOffset = selection.focusOffset;
|
||||
|
||||
var anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
var focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
let anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
let focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
|
@ -192,16 +192,16 @@ function testRTL_selectAll() {
|
|||
*/
|
||||
function testLTR_dragFocusHandleToSelf() {
|
||||
// Select entire LTR Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
var initialSelection =
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var initialSelectionText = sh._getSelectedText();
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag focus handle and note results.
|
||||
sh.observe(null, "TextSelection:Move",
|
||||
|
@ -214,10 +214,10 @@ function testLTR_dragFocusHandleToSelf() {
|
|||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
var focusDraggedSelection =
|
||||
let focusDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var focusDragSelectionText = sh._getSelectedText();
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
@ -250,16 +250,16 @@ function testLTR_dragFocusHandleToSelf() {
|
|||
*/
|
||||
function testLTR_dragAnchorHandleToSelf() {
|
||||
// Select entire LTR Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
var initialSelection =
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var initialSelectionText = sh._getSelectedText();
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag anchor handle and note results.
|
||||
// Note, due to edge case with border boundaries, we actually
|
||||
|
@ -273,10 +273,10 @@ function testLTR_dragAnchorHandleToSelf() {
|
|||
sh.observe(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
var anchorDraggedSelection =
|
||||
let anchorDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var anchorDragSelectionText = sh._getSelectedText();
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
@ -309,16 +309,16 @@ function testLTR_dragAnchorHandleToSelf() {
|
|||
*/
|
||||
function testRTL_dragFocusHandleToSelf() {
|
||||
// Select entire RTL Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
var initialSelection =
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var initialSelectionText = sh._getSelectedText();
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag focus handle and note results.
|
||||
sh.observe(null, "TextSelection:Move",
|
||||
|
@ -331,10 +331,10 @@ function testRTL_dragFocusHandleToSelf() {
|
|||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
var focusDraggedSelection =
|
||||
let focusDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var focusDragSelectionText = sh._getSelectedText();
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
@ -367,16 +367,16 @@ function testRTL_dragFocusHandleToSelf() {
|
|||
*/
|
||||
function testRTL_dragAnchorHandleToSelf() {
|
||||
// Select entire RTL Input element.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLInput");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
var initialSelection =
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var initialSelectionText = sh._getSelectedText();
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag anchor handle and note results.
|
||||
// Note, due to edge case with border boundaries, we actually
|
||||
|
@ -390,10 +390,10 @@ function testRTL_dragAnchorHandleToSelf() {
|
|||
sh.observe(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
var anchorDraggedSelection =
|
||||
let anchorDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var anchorDragSelectionText = sh._getSelectedText();
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
const DIV_POINT_TEXT = "Under";
|
||||
const INPUT_TEXT = "Text for select all in an <input>";
|
||||
|
@ -44,9 +44,9 @@ function startTests() {
|
|||
*
|
||||
*/
|
||||
function testSelectAllDivs() {
|
||||
var sh = getSelectionHandler();
|
||||
var selDiv = document.getElementById("selDiv");
|
||||
var nonSelDiv = document.getElementById("nonSelDiv");
|
||||
let sh = getSelectionHandler();
|
||||
let selDiv = document.getElementById("selDiv");
|
||||
let nonSelDiv = document.getElementById("nonSelDiv");
|
||||
|
||||
// Check the initial state of the selection handler, and selectable/non-selectable <div>s.
|
||||
return Promise.all([
|
||||
|
@ -56,8 +56,8 @@ function testSelectAllDivs() {
|
|||
|
||||
]).then(function() {
|
||||
// Select all on a non-editable text node selects all the text in the page.
|
||||
var startSelectionResult = sh.startSelection(selDiv);
|
||||
var selection = sh._getSelection();
|
||||
let startSelectionResult = sh.startSelection(selDiv);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
|
@ -78,17 +78,17 @@ function testSelectAllDivs() {
|
|||
*
|
||||
*/
|
||||
function testSelectDivAtPoint() {
|
||||
var sh = getSelectionHandler();
|
||||
var selDiv = document.getElementById("selDiv");
|
||||
let sh = getSelectionHandler();
|
||||
let selDiv = document.getElementById("selDiv");
|
||||
|
||||
// Select word at point in <div>
|
||||
var rect = selDiv.getBoundingClientRect();
|
||||
var startSelectionResult = sh.startSelection(selDiv, {
|
||||
let rect = selDiv.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(selDiv, {
|
||||
mode: sh.SELECT_AT_POINT,
|
||||
x: rect.left + 1,
|
||||
y: rect.top + 1
|
||||
});
|
||||
var selection = sh._getSelection();
|
||||
let selection = sh._getSelection();
|
||||
|
||||
// Check the state of the selection handler after selecting at a point.
|
||||
return Promise.all([
|
||||
|
@ -114,8 +114,8 @@ function testSelectDivAtPoint() {
|
|||
*
|
||||
*/
|
||||
function testSelectInput() {
|
||||
var sh = getSelectionHandler();
|
||||
var inputNode = document.getElementById("inputNode");
|
||||
let sh = getSelectionHandler();
|
||||
let inputNode = document.getElementById("inputNode");
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Test that calling startSelection with an input selects all the text in the input.
|
||||
|
@ -125,8 +125,8 @@ function testSelectInput() {
|
|||
|
||||
]).then(function() {
|
||||
// Check the state of the selection handler after calling startSelection on it.
|
||||
var startSelectionResult = sh.startSelection(inputNode);
|
||||
var selection = sh._getSelection();
|
||||
let startSelectionResult = sh.startSelection(inputNode);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
|
@ -145,13 +145,13 @@ function testSelectInput() {
|
|||
*/
|
||||
|
||||
function testSelectTextarea() {
|
||||
var sh = getSelectionHandler();
|
||||
var textareaNode = document.getElementById("textareaNode");
|
||||
let sh = getSelectionHandler();
|
||||
let textareaNode = document.getElementById("textareaNode");
|
||||
textareaNode.value = TEXTAREA_TEXT;
|
||||
|
||||
// Change (still-active) selection from previous <input> field to <textarea>
|
||||
var startSelectionResult = sh.startSelection(textareaNode);
|
||||
var selection = sh._getSelection();
|
||||
let startSelectionResult = sh.startSelection(textareaNode);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active at start of testSelectTextarea"),
|
||||
|
@ -177,8 +177,8 @@ function testSelectTextarea() {
|
|||
*
|
||||
*/
|
||||
function testReadonlyInput() {
|
||||
var sh = getSelectionHandler();
|
||||
var readOnlyNode = document.getElementById("readOnlyTextInput");
|
||||
let sh = getSelectionHandler();
|
||||
let readOnlyNode = document.getElementById("readOnlyTextInput");
|
||||
readOnlyNode.value = READONLY_INPUT_TEXT;
|
||||
|
||||
return Promise.all([
|
||||
|
@ -199,8 +199,8 @@ function testReadonlyInput() {
|
|||
*
|
||||
*/
|
||||
function testCloseSelection() {
|
||||
var sh = getSelectionHandler();
|
||||
var inputNode = document.getElementById("inputNode");
|
||||
let sh = getSelectionHandler();
|
||||
let inputNode = document.getElementById("inputNode");
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Check the initial state of the selection handler.
|
||||
|
@ -239,7 +239,7 @@ function testCloseSelection() {
|
|||
*
|
||||
*/
|
||||
function testStartSelectionFail() {
|
||||
var sh = getSelectionHandler();
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
|
@ -247,9 +247,9 @@ function testStartSelectionFail() {
|
|||
|
||||
]).then(function() {
|
||||
// We cannot perform an invalid selection request.
|
||||
var element = document.getElementById("inputNode");
|
||||
var rect = element.getBoundingClientRect();
|
||||
var startSelectionResult = sh.startSelection(element, {
|
||||
let element = document.getElementById("inputNode");
|
||||
let rect = element.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(element, {
|
||||
mode: "fooMode",
|
||||
x: rect.left + 1,
|
||||
y: rect.top + 1
|
||||
|
@ -263,8 +263,8 @@ function testStartSelectionFail() {
|
|||
|
||||
}).then(function() {
|
||||
// Select all on a Button should fail.
|
||||
var element = document.getElementById("inputButton");
|
||||
var startSelectionResult = sh.startSelection(element);
|
||||
let element = document.getElementById("inputButton");
|
||||
let startSelectionResult = sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.START_ERROR_NONTEXT_INPUT,
|
||||
|
@ -274,9 +274,9 @@ function testStartSelectionFail() {
|
|||
|
||||
}).then(function() {
|
||||
// We cannot Select Word where no point exists.
|
||||
var element = document.getElementById("inputNode");
|
||||
var rect = element.getBoundingClientRect();
|
||||
var startSelectionResult = sh.startSelection(element, {
|
||||
let element = document.getElementById("inputNode");
|
||||
let rect = element.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(element, {
|
||||
mode: sh.SELECT_AT_POINT,
|
||||
x: -1000,
|
||||
y: -1000
|
||||
|
@ -296,15 +296,15 @@ function testStartSelectionFail() {
|
|||
*
|
||||
*/
|
||||
function testAttachCaret() {
|
||||
var sh = getSelectionHandler();
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testAttachCaret"),
|
||||
|
||||
]).then(function() {
|
||||
var element = document.getElementById("inputNode");
|
||||
let element = document.getElementById("inputNode");
|
||||
element.value = INPUT_TEXT;
|
||||
var attachCaretResult = sh.attachCaret(element);
|
||||
let attachCaretResult = sh.attachCaret(element);
|
||||
|
||||
return Promise.all([
|
||||
is(attachCaretResult, sh.ERROR_NONE,
|
||||
|
@ -326,7 +326,7 @@ function testAttachCaret() {
|
|||
*
|
||||
*/
|
||||
function testAttachCaretFail() {
|
||||
var sh = getSelectionHandler();
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
is(sh._activeType, sh.TYPE_NONE,
|
||||
|
@ -334,9 +334,9 @@ function testAttachCaretFail() {
|
|||
|
||||
]).then(function() {
|
||||
// We cannot attach Caret into disabled input.
|
||||
var element = document.getElementById("inputDisabled");
|
||||
let element = document.getElementById("inputDisabled");
|
||||
element.value = INPUT_TEXT;
|
||||
var attachCaretResult = sh.attachCaret(element);
|
||||
let attachCaretResult = sh.attachCaret(element);
|
||||
|
||||
return Promise.all([
|
||||
is(attachCaretResult, sh.ATTACH_ERROR_INCOMPATIBLE,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Used to create handle movement events for SelectionHandler.
|
||||
const ANCHOR = "ANCHOR";
|
||||
|
@ -25,7 +25,7 @@ Cu.import('resource://gre/modules/Geometry.jsm');
|
|||
|
||||
// Distance between text selection lines. Reality tested, and also
|
||||
// Used to perform multi-line selection selections.
|
||||
var selectionLineHeight = 0;
|
||||
let selectionLineHeight = 0;
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
|
@ -60,8 +60,8 @@ function startTests() {
|
|||
*/
|
||||
function testLTR_selectionPoints() {
|
||||
// Select entire LTRTextArea.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
|
@ -71,14 +71,14 @@ function testLTR_selectionPoints() {
|
|||
]).then(function() {
|
||||
// setSelectionRange() (in editable elements), gets us a single-line selection of
|
||||
// midpoint character +- EST_SEL_TEXT_BOUND_CHARS chars on either side.
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Grab values that are cleared by closing selection.
|
||||
var selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
let selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var midpointSelText = sh._getSelectedText();
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
@ -104,8 +104,8 @@ function testLTR_selectionPoints() {
|
|||
*/
|
||||
function testRTL_selectionPoints() {
|
||||
// Select entire RTLTextArea.
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLTextarea");
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
|
@ -115,14 +115,14 @@ function testRTL_selectionPoints() {
|
|||
]).then(function() {
|
||||
// setSelectionRange() (in editable elements), gets us a single-line selection of
|
||||
// midpoint character +- EST_SEL_TEXT_BOUND_CHARS chars on either side.
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Grab values that are cleared by closing selection.
|
||||
var selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
let selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
var midpointSelText = sh._getSelectedText();
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
|
@ -150,14 +150,14 @@ function testRTL_selectionPoints() {
|
|||
* The result is used later to ensure more-precise handle up/down movements.
|
||||
*/
|
||||
function test_selectionLineHeight() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -210,14 +210,14 @@ function test_selectionLineHeight() {
|
|||
* This tests what happens during focus handle down movements.
|
||||
*/
|
||||
function testLTR_moveFocusHandleDown() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -275,14 +275,14 @@ function testLTR_moveFocusHandleDown() {
|
|||
*/
|
||||
|
||||
function testLTR_moveFocusHandleUp() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -343,14 +343,14 @@ function testLTR_moveFocusHandleUp() {
|
|||
* This tests what happens during anchor handle up movements.
|
||||
*/
|
||||
function testLTR_moveAnchorHandleUp() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -407,14 +407,14 @@ function testLTR_moveAnchorHandleUp() {
|
|||
* This tests what happens during anchor handle down movements.
|
||||
*/
|
||||
function testLTR_moveAnchorHandleDown() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("LTRTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -475,14 +475,14 @@ function testLTR_moveAnchorHandleDown() {
|
|||
* This tests what happens during focus handle down movements.
|
||||
*/
|
||||
function testRTL_moveFocusHandleDown() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -539,14 +539,14 @@ function testRTL_moveFocusHandleDown() {
|
|||
* This tests what happens during focus handle up movements.
|
||||
*/
|
||||
function testRTL_moveFocusHandleUp() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -607,14 +607,14 @@ function testRTL_moveFocusHandleUp() {
|
|||
* This tests what happens during anchor handle up movements.
|
||||
*/
|
||||
function testRTL_moveAnchorHandleUp() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
@ -671,14 +671,14 @@ function testRTL_moveAnchorHandleUp() {
|
|||
* This tests what happens during anchor handle down movements.
|
||||
*/
|
||||
function testRTL_moveAnchorHandleDown() {
|
||||
var sh = getSelectionHandler();
|
||||
var element = document.getElementById("RTLTextarea");
|
||||
var initialSelection = null;
|
||||
var changedSelection = null;
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
var midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ var FindHelper = {
|
|||
_targetTab: null,
|
||||
_initialViewport: null,
|
||||
_viewportChanged: false,
|
||||
_matchesCountResult: null,
|
||||
_result: null,
|
||||
_limit: 0,
|
||||
|
||||
observe: function(aMessage, aTopic, aData) {
|
||||
switch(aTopic) {
|
||||
|
@ -31,6 +32,13 @@ var FindHelper = {
|
|||
},
|
||||
|
||||
_findOpened: function() {
|
||||
try {
|
||||
this._limit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
|
||||
} catch (e) {
|
||||
// Pref not available, assume 0, no match counting.
|
||||
this._limit = 0;
|
||||
}
|
||||
|
||||
Messaging.addListener((data) => {
|
||||
this.doFind(data.searchString, data.matchCase);
|
||||
return this._getMatchesCountResult(data.searchString);
|
||||
|
@ -90,18 +98,22 @@ var FindHelper = {
|
|||
* Request, wait for, and return the current matchesCount results for a string.
|
||||
*/
|
||||
_getMatchesCountResult: function(findString) {
|
||||
// Sync call to Finder, results available immediately.
|
||||
this._matchesCountResult = null;
|
||||
this._finder.requestMatchesCount(findString);
|
||||
// Count matches up to any provided limit.
|
||||
if (this._limit <= 0) {
|
||||
return { total: 0, current: 0, limit: 0 };
|
||||
}
|
||||
|
||||
return this._matchesCountResult;
|
||||
// Sync call to Finder, results available immediately.
|
||||
this._finder.requestMatchesCount(findString, this._limit);
|
||||
return this._result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass along the count results to FindInPageBar for display.
|
||||
*/
|
||||
onMatchesCountResult: function(result) {
|
||||
this._matchesCountResult = result;
|
||||
this._result = result;
|
||||
this._result.limit = this._limit;
|
||||
},
|
||||
|
||||
doFind: function(searchString, matchCase) {
|
||||
|
|
|
@ -17,6 +17,11 @@ let Reader = {
|
|||
|
||||
observe: function Reader_observe(aMessage, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "Reader:FetchContent": {
|
||||
let data = JSON.parse(aData);
|
||||
this._fetchContent(data.url, data.id);
|
||||
break;
|
||||
}
|
||||
case "Reader:Removed": {
|
||||
let uri = Services.io.newURI(aData, null, null);
|
||||
ReaderMode.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e));
|
||||
|
@ -178,6 +183,46 @@ let Reader = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Downloads and caches content for a reading list item with a given URL and id.
|
||||
*/
|
||||
_fetchContent: function(url, id) {
|
||||
this._downloadAndCacheArticle(url).then(article => {
|
||||
if (article == null) {
|
||||
Messaging.sendRequest({
|
||||
type: "Reader:UpdateList",
|
||||
id: id,
|
||||
status: this.STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT,
|
||||
});
|
||||
} else {
|
||||
Messaging.sendRequest({
|
||||
type: "Reader:UpdateList",
|
||||
id: id,
|
||||
url: truncate(article.url, MAX_URI_LENGTH),
|
||||
title: truncate(article.title, MAX_TITLE_LENGTH),
|
||||
length: article.length,
|
||||
excerpt: article.excerpt,
|
||||
status: this.STATUS_FETCHED_ARTICLE,
|
||||
});
|
||||
}
|
||||
}).catch(e => {
|
||||
Cu.reportError("Error fetching content: " + e);
|
||||
Messaging.sendRequest({
|
||||
type: "Reader:UpdateList",
|
||||
id: id,
|
||||
status: this.STATUS_FETCH_FAILED_TEMPORARY,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_downloadAndCacheArticle: Task.async(function* (url) {
|
||||
let article = yield ReaderMode.downloadAndParseDocument(url);
|
||||
if (article != null) {
|
||||
yield ReaderMode.storeArticleInCache(article);
|
||||
}
|
||||
return article;
|
||||
}),
|
||||
|
||||
_addTabToReadingList: Task.async(function* (tabID) {
|
||||
let tab = BrowserApp.getTabForId(tabID);
|
||||
if (!tab) {
|
||||
|
|
|
@ -753,6 +753,9 @@ var SelectionHandler = {
|
|||
* @param aX, aY tap location in client coordinates.
|
||||
*/
|
||||
attachCaret: function sh_attachCaret(aElement) {
|
||||
// Clear out any existing active selection
|
||||
this._closeSelection();
|
||||
|
||||
// Ensure it isn't disabled, isn't handled by Android native dialog, and is editable text element
|
||||
if (aElement.disabled || InputWidgetHelper.hasInputWidget(aElement) || !this.isElementEditableText(aElement)) {
|
||||
return this.ATTACH_ERROR_INCOMPATIBLE;
|
||||
|
|
|
@ -146,7 +146,7 @@ let lazilyLoadedObserverScripts = [
|
|||
["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
|
||||
["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
|
||||
["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
|
||||
["Reader", ["Reader:Removed", "Gesture:DoubleTap"], "chrome://browser/content/Reader.js"],
|
||||
["Reader", ["Reader:FetchContent", "Reader:Removed", "Gesture:DoubleTap"], "chrome://browser/content/Reader.js"],
|
||||
];
|
||||
if (AppConstants.MOZ_WEBRTC) {
|
||||
lazilyLoadedObserverScripts.push(
|
||||
|
|
|
@ -554,6 +554,24 @@ function storeCountryCode(cc) {
|
|||
if (cc != "US" && isTimezoneUS) {
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY").add(1);
|
||||
}
|
||||
// telemetry to compare our geoip response with platform-specific country data.
|
||||
// On Mac, we can get a country code via nsIGfxInfo2
|
||||
let gfxInfo2;
|
||||
try {
|
||||
gfxInfo2 = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo2);
|
||||
} catch (ex) {
|
||||
// not on Mac.
|
||||
}
|
||||
if (gfxInfo2) {
|
||||
let macCC = gfxInfo2.countryCode;
|
||||
if (cc == "US" || macCC == "US") {
|
||||
// one of the 2 said US, so record if they are the same.
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX").add(cc != macCC);
|
||||
} else {
|
||||
// different country - record if they are the same
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX").add(cc != macCC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the country we are in via a XHR geoip request.
|
||||
|
|
|
@ -24,6 +24,7 @@ const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE;
|
|||
// nsSearchService.js uses Services.appinfo.name to build a salt for a hash.
|
||||
var XULRuntime = Components.classesByID["{95d89e3e-a169-41a3-8e56-719978e15b12}"]
|
||||
.getService(Ci.nsIXULRuntime);
|
||||
|
||||
var XULAppInfo = {
|
||||
vendor: "Mozilla",
|
||||
name: "XPCShell",
|
||||
|
@ -34,7 +35,8 @@ var XULAppInfo = {
|
|||
platformBuildID: "2007010101",
|
||||
inSafeMode: false,
|
||||
logConsoleErrors: true,
|
||||
OS: "XPCShell",
|
||||
// mirror OS from the base impl as some of the "location" tests rely on it
|
||||
OS: XULRuntime.OS,
|
||||
XPCOMABI: "noarch-spidermonkey",
|
||||
// mirror processType from the base implementation
|
||||
processType: XULRuntime.processType,
|
||||
|
|
|
@ -18,6 +18,34 @@ function run_test() {
|
|||
let histogram = Services.telemetry.getHistogramById(hid);
|
||||
let snapshot = histogram.snapshot();
|
||||
deepEqual(snapshot.counts, [1,0,0]); // boolean probe so 3 buckets, expect 1 result for |0|.
|
||||
|
||||
}
|
||||
|
||||
// simple checks for our platform-specific telemetry. We can't influence
|
||||
// what they return (as we can't influence the countryCode the platform
|
||||
// thinks we are in), but we can check the values are correct given reality.
|
||||
// NOTE: head_search.js mocks the XULRuntime values, but saves the original
|
||||
// OS in an OS global
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
let gfxInfo2 = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo2);
|
||||
print("OSX says the country-code is", gfxInfo2.countryCode);
|
||||
let expectedResult;
|
||||
let hid;
|
||||
// We know geoip said AU - if mac thinks US then we expect
|
||||
// SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX with true (ie, a mismatch)
|
||||
if (gfxInfo2.countryCode == "US") {
|
||||
hid = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX";
|
||||
expectedResult = [0,1,0]; // boolean probe so 3 buckets, expect 1 result for |1|.
|
||||
} else {
|
||||
// We are expecting SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX
|
||||
// with false if OSX says AU (not a mismatch) and true otherwise.
|
||||
hid = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX";
|
||||
expectedResult = gfxInfo2.countryCode == "AU" ? [1,0,0] : [0,1,0];
|
||||
}
|
||||
|
||||
let histogram = Services.telemetry.getHistogramById(hid);
|
||||
let snapshot = histogram.snapshot();
|
||||
deepEqual(snapshot.counts, expectedResult);
|
||||
}
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
|
|
|
@ -4639,6 +4639,18 @@
|
|||
"kind": "flag",
|
||||
"description": "Set if the time-zone heuristic indicates US but the fetched country code doesn't"
|
||||
},
|
||||
"SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX": {
|
||||
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "If we are on OSX and either the OSX countryCode or the geoip countryCode indicates we are in the US, set to false if they both do or true otherwise"
|
||||
},
|
||||
"SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX": {
|
||||
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "If we are on OSX and neither the OSX countryCode nor the geoip countryCode indicates we are in the US, set to false if they both agree on the value or true otherwise"
|
||||
},
|
||||
"SOCIAL_ENABLED_ON_SESSION": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
|
|
|
@ -1752,6 +1752,33 @@ function JSTermHelpers(aOwner)
|
|||
// inert by coercing it to a String.
|
||||
return String(Cu.waiveXrays(aValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy the String representation of a value to the clipboard.
|
||||
*
|
||||
* @param any aValue
|
||||
* A value you want to copy as a string.
|
||||
* @return void
|
||||
*/
|
||||
aOwner.sandbox.copy = function JSTH_copy(aValue)
|
||||
{
|
||||
let payload;
|
||||
try {
|
||||
if (aValue instanceof Ci.nsIDOMElement) {
|
||||
payload = aValue.outerHTML;
|
||||
} else if (typeof aValue == "string") {
|
||||
payload = aValue;
|
||||
} else {
|
||||
payload = JSON.stringify(aValue, null, " ");
|
||||
}
|
||||
} catch (ex) {
|
||||
payload = "/* " + ex + " */";
|
||||
}
|
||||
aOwner.helperResult = {
|
||||
type: "copyValueToClipboard",
|
||||
value: payload,
|
||||
};
|
||||
};
|
||||
}
|
||||
exports.JSTermHelpers = JSTermHelpers;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче