Merge fx-team to m-c a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2015-02-20 18:06:13 -08:00
Родитель a93d947fe1 7cdbc04b44
Коммит 8161ac8243
73 изменённых файлов: 1621 добавлений и 586 удалений

Просмотреть файл

@ -8,6 +8,7 @@
# UITour
host uitour 1 www.mozilla.org
host uitour 1 self-repair.mozilla.org
host uitour 1 support.mozilla.org
host uitour 1 about:home

Просмотреть файл

@ -307,10 +307,11 @@ function onSearchSubmit(aEvent)
if (engineName && searchTerms.length > 0) {
// Send an event that will perform a search and Firefox Health Report will
// record that a search from about:home has occurred.
let useNewTab = aEvent && aEvent.button == 1;
let eventData = {
engineName: engineName,
searchTerms: searchTerms
searchTerms: searchTerms,
useNewTab: useNewTab,
};
if (searchText.hasAttribute("selection-index")) {

Просмотреть файл

@ -1064,3 +1064,38 @@ addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
dataURL: canvas.toDataURL("image/jpeg", ""),
});
});
addMessageListener("ContextMenu:MediaCommand", (message) => {
let media = message.objects.element;
switch (message.data.command) {
case "play":
media.play();
break;
case "pause":
media.pause();
break;
case "mute":
media.muted = true;
break;
case "unmute":
media.muted = false;
break;
case "playbackRate":
media.playbackRate = message.data.data;
break;
case "hidecontrols":
media.removeAttribute("controls");
break;
case "showcontrols":
media.setAttribute("controls", "true");
break;
case "hidestats":
case "showstats":
let event = media.ownerDocument.createEvent("CustomEvent");
event.initCustomEvent("media-showStatistics", false, true,
message.data.command == "showstats");
media.dispatchEvent(event);
break;
}
});

Просмотреть файл

@ -49,11 +49,12 @@ let gSearch = {
let searchText = this._nodes.text;
let searchStr = searchText.value;
if (this.currentEngineName && searchStr.length) {
let useNewTab = event && event.button == 1;
let eventData = {
engineName: this.currentEngineName,
searchString: searchStr,
whence: "newtab",
useNewTab: useNewTab,
}
if (searchText.hasAttribute("selection-index")) {
@ -244,7 +245,7 @@ let gSearch = {
let parent = document.getElementById("newtab-scrollbox");
this._suggestionController =
new SearchSuggestionUIController(this._nodes.text, parent,
() => this.search());
event => this.search(event));
}
this._suggestionController.engineName = engine.name;
},

Просмотреть файл

@ -1668,37 +1668,10 @@ nsContextMenu.prototype = {
},
mediaCommand : function CM_mediaCommand(command, data) {
var media = this.target;
switch (command) {
case "play":
media.play();
break;
case "pause":
media.pause();
break;
case "mute":
media.muted = true;
break;
case "unmute":
media.muted = false;
break;
case "playbackRate":
media.playbackRate = data;
break;
case "hidecontrols":
media.removeAttribute("controls");
break;
case "showcontrols":
media.setAttribute("controls", "true");
break;
case "hidestats":
case "showstats":
var event = media.ownerDocument.createEvent("CustomEvent");
event.initCustomEvent("media-showStatistics", false, true, command == "showstats");
media.dispatchEvent(event);
break;
}
let mm = this.browser.messageManager;
mm.sendAsyncMessage("ContextMenu:MediaCommand",
{command: command, data: data},
{element: this.target});
},
copyMediaLocation : function () {

Просмотреть файл

@ -32,7 +32,8 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
* @param onClick
* A function that's called when a search suggestion is clicked. Ideally
* we could call submit() on inputElement's ancestor form, but that
* doesn't trigger submit listeners.
* doesn't trigger submit listeners. The function is passed one argument,
* the click event.
* @param idPrefix
* The IDs of elements created by the object will be prefixed with this
* string.
@ -234,6 +235,9 @@ SearchSuggestionUIController.prototype = {
},
_onMousedown: function (event) {
if (event.button == 2) {
return;
}
let idx = this._indexOfTableRowOrDescendent(event.target);
let suggestion = this.suggestionAtIndex(idx);
this._stickyInputValue = suggestion;
@ -251,7 +255,7 @@ SearchSuggestionUIController.prototype = {
this.input.setAttribute("selection-kind", "mouse");
this._hideSuggestions();
if (this.onClick) {
this.onClick.call(null);
this.onClick.call(null, event);
}
},

Просмотреть файл

@ -984,7 +984,7 @@
<stylesheet src="chrome://browser/skin/searchbar.css"/>
</resources>
<content ignorekeys="true" level="top" consumeoutsideclicks="never">
<xul:hbox xbl:inherits="collapsed=showonlysettings" anonid="searchbar-engine"
<xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
class="search-panel-header search-panel-current-engine">
<xul:image class="searchbar-engine-image" xbl:inherits="src"/>
<xul:label anonid="searchbar-engine-name" flex="1" crop="end"
@ -1000,8 +1000,7 @@
</xul:tree>
<xul:deck anonid="search-panel-one-offs-header"
selectedIndex="0"
class="search-panel-header search-panel-current-input"
xbl:inherits="hidden=showonlysettings">
class="search-panel-header search-panel-current-input">
<xul:label anonid="searchbar-oneoffheader-search" value="&searchWithHeader.label;"/>
<xul:hbox anonid="search-panel-searchforwith"
class="search-panel-current-input">
@ -1011,11 +1010,9 @@
</xul:hbox>
</xul:deck>
<xul:description anonid="search-panel-one-offs"
class="search-panel-one-offs"
xbl:inherits="hidden=showonlysettings"/>
class="search-panel-one-offs"/>
<xul:vbox anonid="add-engines"/>
<xul:button anonid="search-settings"
xbl:inherits="showonlysettings"
oncommand="BrowserUITelemetry.countSearchSettingsEvent('searchbar');openPreferences('paneSearch')"
class="search-setting-button search-panel-header"
label="&changeSearchSettings.button;"/>

Просмотреть файл

@ -10,6 +10,8 @@ this.EXPORTED_SYMBOLS = ["CustomizableWidgets"];
Cu.import("resource:///modules/CustomizableUI.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
"resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
@ -1012,6 +1014,7 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
this._ensureSanitizer();
this._sanitizer.range = this._getSanitizeRange(doc);
let group = doc.getElementById("PanelUI-panic-timeSpan");
BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
group.selectedItem = doc.getElementById("PanelUI-panic-5min");
let itemsToClear = [
"cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"

Просмотреть файл

@ -730,6 +730,11 @@ let MozLoopServiceInternal = {
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
gLocalizedStrings.set(string.key, string.value);
}
// Supply the strings from the branding bundle on a per-need basis.
let brandBundle =
Services.strings.createBundle("chrome://branding/locale/brand.properties");
// Unfortunately the `brandShortName` string is used by Loop with a lowercase 'N'.
gLocalizedStrings.set("brandShortname", brandBundle.GetStringFromName("brandShortName"));
return gLocalizedStrings;
},

Просмотреть файл

@ -202,6 +202,8 @@ loop.shared.actions = (function() {
* Used to start a screen share.
*/
StartScreenShare: Action.define("startScreenShare", {
// The part of the screen to share, e.g. "window" or "browser".
type: String
}),
/**

Просмотреть файл

@ -89,14 +89,13 @@ loop.OTSdkDriver = (function() {
/**
* Initiates a screen sharing publisher.
*/
startScreenShare: function() {
startScreenShare: function(actionData) {
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
state: SCREEN_SHARE_STATES.PENDING
}));
var config = this._getCopyPublisherConfig();
// This is temporary until we get a sharing type selector
config.videoSource = "window";
config.videoSource = actionData.type;
this.screenshare = this.sdk.initPublisher(this.getScreenShareElementFunc(),
config);

Просмотреть файл

@ -143,14 +143,16 @@ loop.shared.utils = (function(mozL10n) {
return;
}
navigator.mozLoop.composeEmail(
mozL10n.get("share_email_subject4", {
clientShortname: mozL10n.get("clientShortname2")
mozL10n.get("share_email_subject5", {
clientShortname2: mozL10n.get("clientShortname2")
}),
mozL10n.get("share_email_body4", {
mozL10n.get("share_email_body5", {
callUrl: callUrl,
clientShortname: mozL10n.get("clientShortname2"),
brandShortname: mozL10n.get("brandShortname"),
clientShortname2: mozL10n.get("clientShortname2"),
clientSuperShortname: mozL10n.get("clientSuperShortname"),
learnMoreUrl: navigator.mozLoop.getLoopPref("learnMoreUrl")
}),
}).replace(/\r\n/g, "\n").replace(/\n/g, "\r\n"),
recipient
);
}

Просмотреть файл

@ -102,8 +102,18 @@ loop.shared.views = (function(_, l10n) {
}
},
_startScreenShare: function(type) {
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
type: type
}));
},
_handleShareTabs: function() {
this._startScreenShare("browser");
},
_handleShareWindows: function() {
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({}));
this._startScreenShare("window");
},
_getTitle: function() {
@ -143,6 +153,9 @@ loop.shared.views = (function(_, l10n) {
isActive ? null : React.createElement("span", {className: "chevron"})
),
React.createElement("ul", {ref: "menu", className: dropdownMenuClasses},
React.createElement("li", {onClick: this._handleShareTabs, className: "disabled"},
l10n.get("share_tabs_button_title")
),
React.createElement("li", {onClick: this._handleShareWindows},
l10n.get("share_windows_button_title")
)

Просмотреть файл

@ -102,8 +102,18 @@ loop.shared.views = (function(_, l10n) {
}
},
_startScreenShare: function(type) {
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
type: type
}));
},
_handleShareTabs: function() {
this._startScreenShare("browser");
},
_handleShareWindows: function() {
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({}));
this._startScreenShare("window");
},
_getTitle: function() {
@ -143,6 +153,9 @@ loop.shared.views = (function(_, l10n) {
{isActive ? null : <span className="chevron"/>}
</button>
<ul ref="menu" className={dropdownMenuClasses}>
<li onClick={this._handleShareTabs} className="disabled">
{l10n.get("share_tabs_button_title")}
</li>
<li onClick={this._handleShareWindows}>
{l10n.get("share_windows_button_title")}
</li>

Просмотреть файл

@ -139,7 +139,9 @@ describe("loop.OTSdkDriver", function () {
});
it("should dispatch a `ScreenSharingState` action", function() {
driver.startScreenShare(new sharedActions.StartScreenShare());
driver.startScreenShare(new sharedActions.StartScreenShare({
type: "window"
}));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
@ -149,7 +151,9 @@ describe("loop.OTSdkDriver", function () {
});
it("should initialize a publisher", function() {
driver.startScreenShare(new sharedActions.StartScreenShare());
driver.startScreenShare(new sharedActions.StartScreenShare({
type: "window"
}));
sinon.assert.calledOnce(sdk.initPublisher);
sinon.assert.calledWithMatch(sdk.initPublisher,
@ -161,7 +165,9 @@ describe("loop.OTSdkDriver", function () {
beforeEach(function() {
driver.getScreenShareElementFunc = function() {};
driver.startScreenShare(new sharedActions.StartScreenShare());
driver.startScreenShare(new sharedActions.StartScreenShare({
type: "window"
}));
sandbox.stub(dispatcher, "dispatch");
@ -611,7 +617,9 @@ describe("loop.OTSdkDriver", function () {
driver.getScreenShareElementFunc = function() {};
driver.startScreenShare(new sharedActions.StartScreenShare());
driver.startScreenShare(new sharedActions.StartScreenShare({
type: "window"
}));
sandbox.stub(dispatcher, "dispatch");
});

Просмотреть файл

@ -152,8 +152,8 @@ describe("loop.shared.utils", function() {
// fake mozL10n
sandbox.stub(navigator.mozL10n, "get", function(id) {
switch(id) {
case "share_email_subject4": return "subject";
case "share_email_body4": return "body";
case "share_email_subject5": return "subject";
case "share_email_body5": return "body";
}
});
composeEmail = sandbox.spy();

Просмотреть файл

@ -152,7 +152,7 @@ describe("loop.shared.views", function() {
expect(comp.state.showMenu).eql(true);
});
it("should dispatch a StartScreenShare action on option click in screenshare dropdown",
it("should dispatch a 'browser' StartScreenShare action on option click",
function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.ScreenShareControlButton, {
@ -166,7 +166,24 @@ describe("loop.shared.views", function() {
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.StartScreenShare({}));
new sharedActions.StartScreenShare({ type: "browser" }));
});
it("should dispatch a 'window' StartScreenShare action on option click",
function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.ScreenShareControlButton, {
dispatcher: dispatcher,
visible: true,
state: SCREEN_SHARE_STATES.INACTIVE
}));
TestUtils.Simulate.click(comp.getDOMNode().querySelector(
".conversation-window-dropdown > li:last-child"));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.StartScreenShare({ type: "window" }));
});
it("should dispatch a EndScreenShare action on click when the state is active",

Просмотреть файл

@ -29,9 +29,6 @@ skip-if = e10s && (os == 'linux' || os == 'mac') # Bug 1116457
skip-if = e10s
[browser_forgetthissite_single.js]
# disabled for very frequent oranges - bug 551540
skip-if = true
[browser_library_commands.js]
[browser_drag_bookmarks_on_toolbar.js]
skip-if = e10s # Bug ?????? - test fails - "Number of dragged items should be the same. - Got 0, expected 1"

Просмотреть файл

@ -4,6 +4,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Test we correctly migrate Library left pane to the latest version.
* Note: this test MUST be the first between browser chrome tests, or results
@ -12,39 +14,7 @@
const TEST_URI = "http://www.mozilla.org/";
function onLibraryReady(organizer) {
// Check left pane.
ok(PlacesUIUtils.leftPaneFolderId > 0,
"Left pane folder correctly created");
var leftPaneItems =
PlacesUtils.annotations
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(leftPaneItems.length, 1,
"We correctly have only 1 left pane folder");
var leftPaneRoot = leftPaneItems[0];
is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId,
"leftPaneFolderId getter has correct value");
// Check version has been upgraded.
var version =
PlacesUtils.annotations.getItemAnnotation(leftPaneRoot,
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
"Left pane version has been correctly upgraded");
// Check left pane is populated.
organizer.PlacesOrganizer.selectLeftPaneQuery('History');
is(organizer.PlacesOrganizer._places.selectedNode.itemId,
PlacesUIUtils.leftPaneQueries["History"],
"Library left pane is populated and working");
// Close Library window.
organizer.close();
// No need to cleanup anything, we have a correct left pane now.
finish();
}
function test() {
waitForExplicitFinish();
add_task(function* () {
// Sanity checks.
ok(PlacesUtils, "PlacesUtils is running in chrome context");
ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
@ -52,42 +22,69 @@ function test() {
"Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION );
// Check if we have any left pane folder already set, remove it eventually.
var leftPaneItems = PlacesUtils.annotations
let leftPaneItems = PlacesUtils.annotations
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
if (leftPaneItems.length > 0) {
// The left pane has already been created, touching it now would cause
// next tests to rely on wrong values (and possibly crash)
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
// Check version.
var version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0],
let version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0],
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
ok(true, "left pane has already been created, skipping test");
finish();
return;
}
// Create a fake left pane folder with an old version (current version - 1).
var fakeLeftPaneRoot =
PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId, "",
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.annotations.setItemAnnotation(fakeLeftPaneRoot,
let folder = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.rootGuid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: ""
});
let folderId = yield PlacesUtils.promiseItemId(folder.guid);
PlacesUtils.annotations.setItemAnnotation(folderId,
PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1,
0,
PlacesUtils.annotations.EXPIRE_NEVER);
// Check fake left pane root has been correctly created.
var leftPaneItems =
leftPaneItems =
PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
is(leftPaneItems[0], fakeLeftPaneRoot, "left pane root itemId is correct");
is(leftPaneItems[0], folderId, "left pane root itemId is correct");
// Check version.
var version = PlacesUtils.annotations.getItemAnnotation(fakeLeftPaneRoot,
let version = PlacesUtils.annotations.getItemAnnotation(folderId,
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1, "Left pane version correctly set");
// Open Library, this will upgrade our left pane version.
openLibrary(onLibraryReady);
}
let organizer = yield promiseLibrary();
// Check left pane.
ok(PlacesUIUtils.leftPaneFolderId > 0, "Left pane folder correctly created");
leftPaneItems =
PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
let leftPaneRoot = leftPaneItems[0];
is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId,
"leftPaneFolderId getter has correct value");
// Check version has been upgraded.
version = PlacesUtils.annotations.getItemAnnotation(leftPaneRoot,
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
"Left pane version has been correctly upgraded");
// Check left pane is populated.
organizer.PlacesOrganizer.selectLeftPaneQuery("History");
is(organizer.PlacesOrganizer._places.selectedNode.itemId,
PlacesUIUtils.leftPaneQueries["History"],
"Library left pane is populated and working");
yield promiseLibraryClosed(organizer);
});

Просмотреть файл

@ -2,139 +2,113 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function add_bookmark(aURI) {
return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
"bookmark/" + aURI.spec);
}
"use strict";
Components.utils.import("resource://gre/modules/NetUtil.jsm");
const TEST_URL = Services.io.newURI("http://example.com/", null, null);
const MOZURISPEC = Services.io.newURI("http://mozilla.com/", null, null);
const TEST_URL = "http://example.com/";
const MOZURISPEC = "http://mozilla.com/";
add_task(function* () {
let organizer = yield promiseLibrary();
let gLibrary;
let PlacesOrganizer;
let ContentTree;
function test() {
waitForExplicitFinish();
gLibrary = openLibrary(onLibraryReady);
}
function onLibraryReady() {
ok(PlacesUtils, "PlacesUtils in scope");
ok(PlacesUIUtils, "PlacesUIUtils in scope");
PlacesOrganizer = gLibrary.PlacesOrganizer;
let PlacesOrganizer = organizer.PlacesOrganizer;
ok(PlacesOrganizer, "Places organizer in scope");
ContentTree = gLibrary.ContentTree;
let ContentTree = organizer.ContentTree;
ok(ContentTree, "ContentTree is in scope");
tests.makeHistVisit(function() {
tests.makeTag();
tests.focusTag();
waitForClipboard(function(aData) !!aData,
tests.copyHistNode,
onClipboardReady,
PlacesUtils.TYPE_X_MOZ_PLACE);
let visits = {uri: MOZURISPEC, transition: PlacesUtils.history.TRANSITION_TYPED};
yield PlacesTestUtils.addVisits(visits);
// create an initial tag to work with
let bm = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "bookmark/" + TEST_URL.spec,
url: TEST_URL
});
}
function onClipboardReady() {
tests.focusTag();
ok(bm, "A bookmark was added");
PlacesUtils.tagging.tagURI(TEST_URL, ["foo"]);
let tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
is(tags[0], "foo", "tag is foo");
// focus the new tag
focusTag(PlacesOrganizer);
let populate = () => copyHistNode(PlacesOrganizer, ContentTree);
yield promiseClipboard(populate, PlacesUtils.TYPE_X_MOZ_PLACE);
focusTag(PlacesOrganizer);
PlacesOrganizer._places.controller.paste();
tests.historyNode();
tests.checkForBookmarkInUI();
gLibrary.close();
// re-focus the history again
PlacesOrganizer.selectLeftPaneQuery("History");
let histContainer = PlacesOrganizer._places.selectedNode;
PlacesUtils.asContainer(histContainer);
histContainer.containerOpen = true;
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
ok(histNode, "histNode exists: " + histNode.title);
// check to see if the history node is tagged!
tags = PlacesUtils.tagging.getTagsForURI(MOZURISPEC);
ok(tags.length == 1, "history node is tagged: " + tags.length);
// check if a bookmark was created
let bookmarks = [];
yield PlacesUtils.bookmarks.fetch({url: MOZURISPEC}, bm => {
bookmarks.push(bm);
});
ok(bookmarks.length > 0, "bookmark exists for the tagged history item");
// is the bookmark visible in the UI?
// get the Unsorted Bookmarks node
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
// now we can see what is in the ContentTree tree
let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1);
ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri);
is(unsortedNode.uri, MOZURISPEC.spec, "node uri's are the same");
yield promiseLibraryClosed(organizer);
// Remove new Places data we created.
PlacesUtils.tagging.untagURI(NetUtil.newURI(MOZURISPEC), ["foo"]);
PlacesUtils.tagging.untagURI(NetUtil.newURI(TEST_URL), ["foo"]);
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
PlacesUtils.tagging.untagURI(MOZURISPEC, ["foo"]);
PlacesUtils.tagging.untagURI(TEST_URL, ["foo"]);
tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
is(tags.length, 0, "tags are gone");
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
PlacesTestUtils.clearHistory().then(finish);
yield PlacesUtils.bookmarks.eraseEverything();
yield PlacesTestUtils.clearHistory();
});
function focusTag(PlacesOrganizer) {
PlacesOrganizer.selectLeftPaneQuery("Tags");
let tags = PlacesOrganizer._places.selectedNode;
tags.containerOpen = true;
let fooTag = tags.getChild(0);
let tagNode = fooTag;
PlacesOrganizer._places.selectNode(fooTag);
is(tagNode.title, 'foo', "tagNode title is foo");
let ip = PlacesOrganizer._places.insertionPoint;
ok(ip.isTag, "IP is a tag");
}
let tests = {
makeHistVisit: function(aCallback) {
// need to add a history object
let testURI1 = NetUtil.newURI(MOZURISPEC);
isnot(testURI1, null, "testURI is not null");
PlacesTestUtils.addVisits(
{uri: testURI1, transition: PlacesUtils.history.TRANSITION_TYPED}
).then(aCallback);
},
makeTag: function() {
// create an initial tag to work with
let bmId = add_bookmark(NetUtil.newURI(TEST_URL));
ok(bmId > 0, "A bookmark was added");
PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]);
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
is(tags[0], 'foo', "tag is foo");
},
focusTag: function (){
// focus the new tag
PlacesOrganizer.selectLeftPaneQuery("Tags");
let tags = PlacesOrganizer._places.selectedNode;
tags.containerOpen = true;
let fooTag = tags.getChild(0);
let tagNode = fooTag;
PlacesOrganizer._places.selectNode(fooTag);
is(tagNode.title, 'foo', "tagNode title is foo");
let ip = PlacesOrganizer._places.insertionPoint;
ok(ip.isTag, "IP is a tag");
},
copyHistNode: function (){
// focus the history object
PlacesOrganizer.selectLeftPaneQuery("History");
let histContainer = PlacesOrganizer._places.selectedNode;
PlacesUtils.asContainer(histContainer);
histContainer.containerOpen = true;
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
ContentTree.view.selectNode(histNode);
is(histNode.uri, MOZURISPEC,
"historyNode exists: " + histNode.uri);
// copy the history node
ContentTree.view.controller.copy();
},
historyNode: function (){
// re-focus the history again
PlacesOrganizer.selectLeftPaneQuery("History");
let histContainer = PlacesOrganizer._places.selectedNode;
PlacesUtils.asContainer(histContainer);
histContainer.containerOpen = true;
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
ok(histNode, "histNode exists: " + histNode.title);
// check to see if the history node is tagged!
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(MOZURISPEC));
ok(tags.length == 1, "history node is tagged: " + tags.length);
// check if a bookmark was created
let isBookmarked = PlacesUtils.bookmarks.isBookmarked(NetUtil.newURI(MOZURISPEC));
is(isBookmarked, true, MOZURISPEC + " is bookmarked");
let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(
NetUtil.newURI(histNode.uri));
ok(bookmarkIds.length > 0, "bookmark exists for the tagged history item: " + bookmarkIds);
},
checkForBookmarkInUI: function(){
// is the bookmark visible in the UI?
// get the Unsorted Bookmarks node
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
// now we can see what is in the ContentTree tree
let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1);
ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri);
is(unsortedNode.uri, MOZURISPEC, "node uri's are the same");
},
};
function copyHistNode(PlacesOrganizer, ContentTree) {
// focus the history object
PlacesOrganizer.selectLeftPaneQuery("History");
let histContainer = PlacesOrganizer._places.selectedNode;
PlacesUtils.asContainer(histContainer);
histContainer.containerOpen = true;
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
ContentTree.view.selectNode(histNode);
is(histNode.uri, MOZURISPEC.spec,
"historyNode exists: " + histNode.uri);
// copy the history node
ContentTree.view.controller.copy();
}

Просмотреть файл

@ -1,22 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URL = "http://example.com/";
let gLibrary;
let gItemId;
let PlacesOrganizer;
let ContentTree;
function test() {
waitForExplicitFinish();
gLibrary = openLibrary(onLibraryReady);
}
function onLibraryReady() {
PlacesOrganizer = gLibrary.PlacesOrganizer;
ContentTree = gLibrary.ContentTree;
add_task(function* () {
let organizer = yield promiseLibrary();
let PlacesOrganizer = organizer.PlacesOrganizer;
let ContentTree = organizer.ContentTree;
// Sanity checks.
ok(PlacesUtils, "PlacesUtils in scope");
@ -24,49 +16,44 @@ function onLibraryReady() {
ok(PlacesOrganizer, "PlacesOrganizer in scope");
ok(ContentTree, "ContentTree is in scope");
gItemId = PlacesUtils.bookmarks.insertBookmark(
PlacesUtils.toolbarFolderId, NetUtil.newURI(TEST_URL),
PlacesUtils.bookmarks.DEFAULT_INDEX, "test"
);
let bm = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: TEST_URL
});
selectBookmarkIn("BookmarksToolbar");
yield selectBookmarkIn(organizer, bm, "BookmarksToolbar");
waitForClipboard(function(aData) !!aData,
cutSelection,
onClipboardReady,
PlacesUtils.TYPE_X_MOZ_PLACE);
}
yield promiseClipboard(() => {
info("Cutting selection");
ContentTree.view.controller.cut();
}, PlacesUtils.TYPE_X_MOZ_PLACE);
info("Selecting UnfiledBookmarks in the left pane");
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
info("Pasting clipboard");
ContentTree.view.controller.paste();
yield selectBookmarkIn(organizer, bm, "UnfiledBookmarks");
yield promiseLibraryClosed(organizer);
yield PlacesUtils.bookmarks.eraseEverything();
});
let selectBookmarkIn = Task.async(function* (organizer, bm, aLeftPaneQuery) {
let PlacesOrganizer = organizer.PlacesOrganizer;
let ContentTree = organizer.ContentTree;
function selectBookmarkIn(aLeftPaneQuery) {
info("Selecting " + aLeftPaneQuery + " in the left pane");
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
let rootId = PlacesUtils.getConcreteItemId(PlacesOrganizer._places.selectedNode);
is(PlacesUtils.bookmarks.getFolderIdForItem(gItemId), rootId,
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
is((yield PlacesUtils.promiseItemId(bm.parentGuid)), rootId,
"Bookmark has the right parent");
info("Selecting the bookmark in the right pane");
ContentTree.view.selectItems([gItemId]);
ContentTree.view.selectItems([yield PlacesUtils.promiseItemId(bm.guid)]);
let bookmarkNode = ContentTree.view.selectedNode;
is(bookmarkNode.uri, TEST_URL, "Found the expected bookmark");
}
function cutSelection() {
info("Cutting selection");
ContentTree.view.controller.cut();
}
function pasteClipboard(aLeftPaneQuery) {
info("Selecting " + aLeftPaneQuery + " in the left pane");
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
info("Pasting clipboard");
ContentTree.view.controller.paste();
}
function onClipboardReady() {
pasteClipboard("UnfiledBookmarks");
selectBookmarkIn("UnfiledBookmarks");
// Cleanup.
gLibrary.close();
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
finish();
}
});

Просмотреть файл

@ -1,54 +1,66 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
"use strict";
/*
Deep copy of bookmark data, using the front-end codepath:
- create test folder A
- add a subfolder to folder A, and add items to it
- validate folder A (sanity check)
- copy folder A, creating new folder B, using the front-end path
- validate folder B
- undo copy transaction
- validate folder B (empty)
- redo copy transaction
- validate folder B's contents
*/
add_task(function* () {
// sanity check
ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
/*
Deep copy of bookmark data, using the front-end codepath:
let toolbarId = PlacesUtils.toolbarFolderId;
let toolbarNode = PlacesUtils.getFolderContents(toolbarId).root;
- create test folder A
- add a subfolder to folder A, and add items to it
- validate folder A (sanity check)
- copy folder A, creating new folder B, using the front-end path
- validate folder B
- undo copy transaction
- validate folder B (empty)
- redo copy transaction
- validate folder B's contents
*/
var toolbarId = PlacesUtils.toolbarFolderId;
var toolbarNode = PlacesUtils.getFolderContents(toolbarId).root;
var oldCount = toolbarNode.childCount;
var testRootId = PlacesUtils.bookmarks.createFolder(toolbarId, "test root", -1);
let oldCount = toolbarNode.childCount;
let testRoot = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "test root"
});
is(toolbarNode.childCount, oldCount+1, "confirm test root node is a container, and is empty");
var testRootNode = toolbarNode.getChild(toolbarNode.childCount-1);
let testRootNode = toolbarNode.getChild(toolbarNode.childCount-1);
testRootNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
testRootNode.containerOpen = true;
is(testRootNode.childCount, 0, "confirm test root node is a container, and is empty");
// create folder A, fill it, validate its contents
var folderAId = PlacesUtils.bookmarks.createFolder(testRootId, "A", -1);
populate(folderAId);
var folderANode = PlacesUtils.getFolderContents(folderAId).root;
let folderA = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: testRoot.guid,
title: "A"
});
yield populate(folderA);
let folderAId = yield PlacesUtils.promiseItemId(folderA.guid);
let folderANode = PlacesUtils.getFolderContents(folderAId).root;
validate(folderANode);
is(testRootNode.childCount, 1, "create test folder");
// copy it, using the front-end helper functions
var serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
var rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
let serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
let rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
// confirm serialization
ok(rawNode.type, "confirm json node");
folderANode.containerOpen = false;
var transaction = PlacesUIUtils.makeTransaction(rawNode,
let testRootId = yield PlacesUtils.promiseItemId(testRoot.guid);
let transaction = PlacesUIUtils.makeTransaction(rawNode,
PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
testRootId,
-1,
@ -59,7 +71,7 @@ function test() {
is(testRootNode.childCount, 2, "create test folder via copy");
// validate the copy
var folderBNode = testRootNode.getChild(1);
let folderBNode = testRootNode.getChild(1);
validate(folderBNode);
// undo the transaction, confirm the removal
@ -78,14 +90,28 @@ function test() {
// clean up
PlacesUtils.transactionManager.undoTransaction();
PlacesUtils.bookmarks.removeItem(folderAId);
}
yield PlacesUtils.bookmarks.remove(folderA.guid);
});
function populate(aFolderId) {
var folderId = PlacesUtils.bookmarks.createFolder(aFolderId, "test folder", -1);
PlacesUtils.bookmarks.insertBookmark(folderId, PlacesUtils._uri("http://foo"), -1, "test bookmark");
PlacesUtils.bookmarks.insertSeparator(folderId, -1);
}
let populate = Task.async(function* (parentFolder) {
let folder = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: parentFolder.guid,
title: "test folder"
});
yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder.guid,
title: "test bookmark",
url: "http://foo"
});
yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: folder.guid
});
});
function validate(aNode) {
PlacesUtils.asContainer(aNode);

Просмотреть файл

@ -2,69 +2,77 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const TEST_URIs = [
"http://www.mozilla.org/test1",
"http://www.mozilla.org/test2"
];
// This test makes sure that the Forget This Site command is hidden for multiple
// selections.
function test() {
// initialization
waitForExplicitFinish();
add_task(function* () {
// Add a history entry.
let TEST_URIs = ["http://www.mozilla.org/test1", "http://www.mozilla.org/test2"];
ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
let places = [];
TEST_URIs.forEach(function(TEST_URI) {
places.push({uri: PlacesUtils._uri(TEST_URI),
transition: PlacesUtils.history.TRANSITION_TYPED});
});
PlacesTestUtils.addVisits(places).then(() => {
testForgetThisSiteVisibility(1, function() {
testForgetThisSiteVisibility(2, function() {
// Cleanup
PlacesTestUtils.clearHistory().then(finish);
});
});
});
function testForgetThisSiteVisibility(selectionCount, funcNext) {
openLibrary(function (organizer) {
// Select History in the left pane.
organizer.PlacesOrganizer.selectLeftPaneQuery('History');
let PO = organizer.PlacesOrganizer;
let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
histContainer.containerOpen = true;
PO._places.selectNode(histContainer.getChild(0));
// Select the first history entry.
let doc = organizer.document;
let tree = PO._content;
let selection = tree.view.selection;
selection.clearSelection();
selection.rangedSelect(0, selectionCount - 1, true);
is(selection.count, selectionCount,
"The selected range is as big as expected");
// Open the context menu
let contextmenu = doc.getElementById("placesContext");
contextmenu.addEventListener("popupshown", function() {
contextmenu.removeEventListener("popupshown", arguments.callee, true);
let forgetThisSite = doc.getElementById("placesContext_deleteHost");
let hideForgetThisSite = (selectionCount != 1);
is(forgetThisSite.hidden, hideForgetThisSite,
"The Forget this site menu item should " + (hideForgetThisSite ? "" : "not ") +
"be hidden with " + selectionCount + " items selected");
// Close the context menu
contextmenu.hidePopup();
// Wait for the Organizer window to actually be closed
organizer.addEventListener("unload", function () {
organizer.removeEventListener("unload", arguments.callee, false);
// Proceed
funcNext();
}, false);
// Close Library window.
organizer.close();
}, true);
// Get cell coordinates
var rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text");
// Initiate a context menu for the selected cell
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu"}, organizer);
});
}
let places = [];
let transition = PlacesUtils.history.TRANSITION_TYPED;
TEST_URIs.forEach(uri => places.push({uri: PlacesUtils._uri(uri), transition}));
yield PlacesTestUtils.addVisits(places);
yield testForgetThisSiteVisibility(1);
yield testForgetThisSiteVisibility(2);
// Cleanup.
yield PlacesTestUtils.clearHistory();
});
let testForgetThisSiteVisibility = Task.async(function* (selectionCount) {
let organizer = yield promiseLibrary();
// Select History in the left pane.
organizer.PlacesOrganizer.selectLeftPaneQuery("History");
let PO = organizer.PlacesOrganizer;
let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
histContainer.containerOpen = true;
PO._places.selectNode(histContainer.getChild(0));
// Select the first history entry.
let doc = organizer.document;
let tree = doc.getElementById("placeContent");
let selection = tree.view.selection;
selection.clearSelection();
selection.rangedSelect(0, selectionCount - 1, true);
is(selection.count, selectionCount, "The selected range is as big as expected");
// Open the context menu.
let contextmenu = doc.getElementById("placesContext");
let popupShown = promisePopupShown(contextmenu);
// Get cell coordinates.
let rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text");
// Initiate a context menu for the selected cell.
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu", button: 2}, organizer);
yield popupShown;
let forgetThisSite = doc.getElementById("placesContext_deleteHost");
let hideForgetThisSite = (selectionCount != 1);
is(forgetThisSite.hidden, hideForgetThisSite,
`The Forget this site menu item should ${hideForgetThisSite ? "" : "not "}` +
` be hidden with ${selectionCount} items selected`);
// Close the context menu.
contextmenu.hidePopup();
// Close the library window.
yield promiseLibraryClosed(organizer);
});
function promisePopupShown(popup) {
return new Promise(resolve => {
popup.addEventListener("popupshown", function onShown() {
popup.removeEventListener("popupshown", onShown, true);
resolve();
}, true);
});
}

Просмотреть файл

@ -43,17 +43,31 @@ function openLibrary(callback, aLeftPaneRoot) {
* Hierarchy to open and select in the left pane.
*/
function promiseLibrary(aLeftPaneRoot) {
let deferred = Promise.defer();
let library = Services.wm.getMostRecentWindow("Places:Organizer");
if (library && !library.closed) {
if (aLeftPaneRoot)
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
deferred.resolve(library);
}
else {
openLibrary(aLibrary => deferred.resolve(aLibrary), aLeftPaneRoot);
}
return deferred.promise;
return new Promise(resolve => {
let library = Services.wm.getMostRecentWindow("Places:Organizer");
if (library && !library.closed) {
if (aLeftPaneRoot) {
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
}
resolve(library);
}
else {
openLibrary(resolve, aLeftPaneRoot);
}
});
}
function promiseLibraryClosed(organizer) {
return new Promise(resolve => {
// Wait for the Organizer window to actually be closed
organizer.addEventListener("unload", function onUnload() {
organizer.removeEventListener("unload", onUnload);
resolve();
});
// Close Library window.
organizer.close();
});
}
/**
@ -67,12 +81,9 @@ function promiseLibrary(aLeftPaneRoot) {
* Data flavor to expect.
*/
function promiseClipboard(aPopulateClipboardFn, aFlavor) {
let deferred = Promise.defer();
waitForClipboard(function (aData) !!aData,
aPopulateClipboardFn,
function () { deferred.resolve(); },
aFlavor);
return deferred.promise;
return new Promise(resolve => {
waitForClipboard(data => !!data, aPopulateClipboardFn, resolve, aFlavor);
});
}
/**

Просмотреть файл

@ -108,6 +108,37 @@ var gFontsDialog = {
{
var useDocumentFonts = document.getElementById("useDocumentFonts");
return useDocumentFonts.checked ? 1 : 0;
}
},
onBeforeAccept: function ()
{
// Only care in in-content prefs
if (!window.frameElement) {
return true;
}
let preferences = document.querySelectorAll("preference[id*='font.minimum-size']");
// It would be good if we could avoid touching languages the pref pages won't use, but
// unfortunately the language group APIs (deducing language groups from language codes)
// are C++ - only. So we just check all the things the user touched:
// Don't care about anything up to 24px, or if this value is the same as set previously:
preferences = Array.filter(preferences, prefEl => {
return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences;
});
if (!preferences.length) {
return;
}
let strings = document.getElementById("bundlePreferences");
let title = strings.getString("veryLargeMinimumFontTitle");
let confirmLabel = strings.getString("acceptVeryLargeMinimumFont");
let warningMessage = strings.getString("veryLargeMinimumFontWarning");
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING |
Services.prompt.BUTTON_POS_1_DEFAULT;
let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {});
return buttonChosen == 0;
},
};

Просмотреть файл

@ -17,6 +17,7 @@
title="&fontsDialog.title;"
dlgbuttons="accept,cancel,help"
ondialoghelp="openPrefsHelp()"
onbeforeaccept="return gFontsDialog.onBeforeAccept();"
style="">
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>

Просмотреть файл

@ -1063,6 +1063,12 @@
let selectedButton = this.selectedButton;
let buttons = this.getSelectableButtons(aCycleEngines);
let suggestionsHidden;
if (!aSkipSuggestions && popup.hasAttribute("showonlysettings")) {
aSkipSuggestions = true;
suggestionsHidden = true;
}
// If the last suggestion is selected, DOWN selects the first button.
if (!aSkipSuggestions && aForward &&
popup.selectedIndex + 1 == popup.view.rowCount) {
@ -1083,7 +1089,7 @@
else
this.selectedButton = null;
if (this.selectedButton || aCycleEngines)
if (this.selectedButton || aCycleEngines || suggestionsHidden)
return true;
// Set the selectedIndex to something that will make
@ -1104,7 +1110,7 @@
return true;
}
if (!aForward && (aCycleEngines ||
if (!aForward && (aCycleEngines || suggestionsHidden ||
(!aSkipSuggestions && popup.selectedIndex == -1))) {
// the last button.
this.selectedButton = buttons[buttons.length - 1];
@ -1123,15 +1129,6 @@
if (!popup.popupOpen)
return;
if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN &&
popup.hasAttribute("showonlysettings")) {
// If the suggestion tree is collapsed, don't let the down arrow
// key event reach the tree.
aEvent.preventDefault();
aEvent.stopPropagation();
return;
}
let list = document.getAnonymousElementByAttribute(popup, "anonid",
"search-panel-one-offs");
if (!list) // remove this check when removing the old search UI.

Просмотреть файл

@ -44,4 +44,5 @@ skip-if = e10s || true # Bug ??????, Bug 1100301 - leaks windows until shutdown
[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]
[browser_searchbar_smallpanel_keyboard_navigation.js]
[browser_webapi.js]

Просмотреть файл

@ -191,6 +191,27 @@ add_task(function* focus_change_closes_popup() {
textbox.value = "";
});
// Moving focus away from the search box should close the small popup
add_task(function* focus_change_closes_small_popup() {
gURLBar.focus();
let promise = promiseEvent(searchPopup, "popupshown");
// For some reason sending the mouse event immediately doesn't open the popup.
SimpleTest.executeSoon(() => {
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
});
yield promise;
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
promise = promiseEvent(searchPopup, "popuphidden");
let promise2 = promiseEvent(searchbar, "blur");
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
yield promise;
yield promise2;
});
// Pressing escape should close the popup.
add_task(function* escape_closes_popup() {
gURLBar.focus();
@ -442,4 +463,6 @@ add_task(function* dont_rollup_oncaretmove() {
promise = promiseEvent(searchPopup, "popuphidden");
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield promise;
textbox.value = "";
});

Просмотреть файл

@ -0,0 +1,377 @@
// 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 searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
"searchbar-search-button");
const kValues = ["foo1", "foo2", "foo3"];
// 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
});
});
});
add_task(function* test_arrows() {
let promise = promiseEvent(searchPopup, "popupshown");
info("Opening search panel");
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
yield promise;
info("textbox.mController.searchString = " + textbox.mController.searchString);
is(textbox.mController.searchString, "", "The search string should be empty");
// Check the initial state of the panel before sending keyboard events.
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
// Having suggestions populated (but hidden) is important, because if there
// are none we can't ensure the keyboard events don't reach them.
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");
// Pressing should select the first one-off.
EventUtils.synthesizeKey("VK_DOWN", {});
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
is(textbox.value, "", "the textfield value should be unmodified");
// 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.
EventUtils.synthesizeKey("VK_UP", {});
ok(!textbox.selectedButton, "no one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
is(textbox.value, "", "the textfield value should be unmodified");
});
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, "", "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");
SimpleTest.executeSoon(() => {
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
});
yield promise;
let oneOffs = getOneOffs();
ok(!textbox.selectedButton, "no one-off button should be selected");
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
// 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, "", "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 reopen the panel.
let promise = promiseEvent(searchPopup, "popupshown");
info("Opening search panel");
SimpleTest.executeSoon(() => {
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
});
yield promise;
// and check it's in a correct initial state.
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
ok(!textbox.selectedButton, "no one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
is(textbox.value, "", "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");
// Clear the selection with an alt+up keypress
EventUtils.synthesizeKey("VK_UP", {altKey: true});
ok(!textbox.selectedButton, "no one-off button should be selected");
});
add_task(function* test_alt_up() {
// Check the initial state of the panel
ok(!textbox.selectedButton, "no one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
is(textbox.value, "", "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, "", "the textfield value should be unmodified");
// After pressing down, the first one-off should be selected.
let oneOffs = getOneOffs();
EventUtils.synthesizeKey("VK_DOWN", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing tab, the second one-off should be selected.
EventUtils.synthesizeKey("VK_TAB", {});
is(textbox.selectedButton, oneOffs[1],
"the second one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing up, the first one-off should be selected again.
EventUtils.synthesizeKey("VK_UP", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// 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");
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
yield promise;
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
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();
});

Просмотреть файл

@ -136,9 +136,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* - staged: true to stage the item to be appended later
*/
addSource: function(aSource, aOptions = {}) {
if (!(aSource.url || aSource.introductionUrl)) {
// These would be most likely eval scripts introduced in inline
// JavaScript in HTML, and we don't show those yet (bug 1097873)
if (!aSource.url) {
// We don't show any unnamed eval scripts yet (see bug 1124106)
return;
}
@ -170,21 +169,10 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
},
_parseUrl: function(aSource) {
let fullUrl = aSource.url || aSource.introductionUrl;
let fullUrl = aSource.url;
let url = fullUrl.split(" -> ").pop();
let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
let group;
if (!aSource.url && aSource.introductionUrl) {
label += ' > ' + aSource.introductionType;
group = L10N.getStr("evalGroupLabel");
}
else if(aSource.addonID) {
group = aSource.addonID;
}
else {
group = SourceUtils.getSourceGroup(url);
}
let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
return {
label: label,

Просмотреть файл

@ -407,7 +407,7 @@ skip-if = e10s # Bug 1093535
[browser_dbg_sources-cache.js]
skip-if = e10s && debug
[browser_dbg_sources-eval-01.js]
skip-if = e10s && debug
skip-if = true # non-named eval sources turned off for now, bug 1124106
[browser_dbg_sources-eval-02.js]
skip-if = e10s && debug
[browser_dbg_sources-labels.js]

Просмотреть файл

@ -28,16 +28,16 @@ function test() {
function run() {
return Task.spawn(function*() {
let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
callInTab(gTab, "evalSource");
callInTab(gTab, "evalSourceWithSourceURL");
yield newSource;
yield gPanel.addBreakpoint({ actor: gSources.values[1], line: 2 });
yield gPanel.addBreakpoint({ actor: gSources.values[0], line: 2 });
yield ensureThreadClientState(gPanel, "resumed");
const paused = waitForThreadEvents(gPanel, "paused");
callInTab(gTab, "bar");
let frame = (yield paused).frame;
is(frame.where.source.actor, gSources.values[1], "Should have broken on the eval'ed source");
is(frame.where.source.actor, gSources.values[0], "Should have broken on the eval'ed source");
is(frame.where.line, 2, "Should break on line 2");
yield resumeDebuggerThenCloseAndFinish(gPanel);

Просмотреть файл

@ -1,4 +1,4 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.0.1130
Current extension version is: 1.0.1149

Просмотреть файл

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.0.1130';
PDFJS.build = 'e4f0ae2';
PDFJS.version = '1.0.1149';
PDFJS.build = 'bc7a110';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -356,6 +356,7 @@ function shadow(obj, prop, value) {
writable: false });
return value;
}
PDFJS.shadow = shadow;
var PasswordResponses = PDFJS.PasswordResponses = {
NEED_PASSWORD: 1,

Просмотреть файл

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.0.1130';
PDFJS.build = 'e4f0ae2';
PDFJS.version = '1.0.1149';
PDFJS.build = 'bc7a110';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -356,6 +356,7 @@ function shadow(obj, prop, value) {
writable: false });
return value;
}
PDFJS.shadow = shadow;
var PasswordResponses = PDFJS.PasswordResponses = {
NEED_PASSWORD: 1,
@ -4800,7 +4801,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
data.annotationType = AnnotationType.LINK;
var action = dict.get('A');
if (action) {
if (action && isDict(action)) {
var linkType = action.get('S').name;
if (linkType === 'URI') {
var url = action.get('URI');
@ -15743,6 +15744,10 @@ var ToUnicodeMap = (function ToUnicodeMapClosure() {
}
},
has: function(i) {
return this._map[i] !== undefined;
},
get: function(i) {
return this._map[i];
},
@ -15772,6 +15777,10 @@ var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
}
},
has: function (i) {
return this.firstChar <= i && i <= this.lastChar;
},
get: function (i) {
if (this.firstChar <= i && i <= this.lastChar) {
return String.fromCharCode(i);
@ -16204,7 +16213,6 @@ var Font = (function FontClosure() {
var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
var isIdentityUnicode =
properties.toUnicode instanceof IdentityToUnicodeMap;
var isCidFontType2 = (properties.type === 'CIDFontType2');
var newMap = Object.create(null);
var toFontChar = [];
var usedFontCharCodes = [];
@ -16215,17 +16223,11 @@ var Font = (function FontClosure() {
var fontCharCode = originalCharCode;
// First try to map the value to a unicode position if a non identity map
// was created.
if (!isIdentityUnicode) {
if (toUnicode.get(originalCharCode) !== undefined) {
var unicode = toUnicode.get(fontCharCode);
// TODO: Try to map ligatures to the correct spot.
if (unicode.length === 1) {
fontCharCode = unicode.charCodeAt(0);
}
} else if (isCidFontType2) {
// For CIDFontType2, move characters not present in toUnicode
// to the private use area (fixes bug 1028735 and issue 4881).
fontCharCode = nextAvailableFontCharCode;
if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
var unicode = toUnicode.get(fontCharCode);
// TODO: Try to map ligatures to the correct spot.
if (unicode.length === 1) {
fontCharCode = unicode.charCodeAt(0);
}
}
// Try to move control characters, special characters and already mapped
@ -16715,13 +16717,20 @@ var Font = (function FontClosure() {
var offset = font.getInt32() >>> 0;
var useTable = false;
if (platformId === 1 && encodingId === 0) {
if (platformId === 0 && encodingId === 0) {
useTable = true;
// Continue the loop since there still may be a higher priority
// table.
} else if (!isSymbolicFont && platformId === 3 && encodingId === 1) {
} else if (platformId === 1 && encodingId === 0) {
useTable = true;
canBreak = true;
// Continue the loop since there still may be a higher priority
// table.
} else if (platformId === 3 && encodingId === 1 &&
(!isSymbolicFont || !potentialTable)) {
useTable = true;
if (!isSymbolicFont) {
canBreak = true;
}
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
useTable = true;
canBreak = true;
@ -17066,6 +17075,7 @@ var Font = (function FontClosure() {
var newGlyfData = new Uint8Array(oldGlyfDataLength);
var startOffset = itemDecode(locaData, 0);
var writeOffset = 0;
var missingGlyphData = {};
itemEncode(locaData, 0, writeOffset);
var i, j;
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
@ -17083,6 +17093,10 @@ var Font = (function FontClosure() {
continue;
}
if (startOffset === endOffset) {
missingGlyphData[i] = true;
}
var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
newGlyfData, writeOffset, hintsValid);
writeOffset += newLength;
@ -17099,7 +17113,7 @@ var Font = (function FontClosure() {
itemEncode(locaData, j, simpleGlyph.length);
}
glyf.data = simpleGlyph;
return;
return missingGlyphData;
}
if (dupFirstEntry) {
@ -17116,6 +17130,7 @@ var Font = (function FontClosure() {
} else {
glyf.data = newGlyfData.subarray(0, writeOffset);
}
return missingGlyphData;
}
function readPostScriptTable(post, properties, maxpNumGlyphs) {
@ -17575,11 +17590,13 @@ var Font = (function FontClosure() {
sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
var missingGlyphs = {};
if (isTrueType) {
var isGlyphLocationsLong = int16(tables.head.data[50],
tables.head.data[51]);
sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs,
isGlyphLocationsLong, hintsValid, dupFirstEntry);
missingGlyphs = sanitizeGlyphLocations(tables.loca, tables.glyf,
numGlyphs, isGlyphLocationsLong,
hintsValid, dupFirstEntry);
}
if (!tables.hhea) {
@ -17601,19 +17618,33 @@ var Font = (function FontClosure() {
}
}
var charCodeToGlyphId = [], charCode;
var charCodeToGlyphId = [], charCode, toUnicode = properties.toUnicode;
function hasGlyph(glyphId, charCode) {
if (!missingGlyphs[glyphId]) {
return true;
}
if (charCode >= 0 && toUnicode.has(charCode)) {
return true;
}
return false;
}
if (properties.type === 'CIDFontType2') {
var cidToGidMap = properties.cidToGidMap || [];
var cidToGidMapLength = cidToGidMap.length;
var isCidToGidMapEmpty = cidToGidMap.length === 0;
properties.cMap.forEach(function(charCode, cid) {
assert(cid <= 0xffff, 'Max size of CID is 65,535');
var glyphId = -1;
if (cidToGidMapLength === 0) {
if (isCidToGidMapEmpty) {
glyphId = charCode;
} else if (cidToGidMap[cid] !== undefined) {
glyphId = cidToGidMap[cid];
}
if (glyphId >= 0 && glyphId < numGlyphs) {
if (glyphId >= 0 && glyphId < numGlyphs &&
hasGlyph(glyphId, charCode)) {
charCodeToGlyphId[charCode] = glyphId;
}
});
@ -17673,7 +17704,8 @@ var Font = (function FontClosure() {
var found = false;
for (i = 0; i < cmapMappingsLength; ++i) {
if (cmapMappings[i].charCode === unicodeOrCharCode) {
if (cmapMappings[i].charCode === unicodeOrCharCode &&
hasGlyph(cmapMappings[i].glyphId, unicodeOrCharCode)) {
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
found = true;
break;
@ -17683,11 +17715,17 @@ var Font = (function FontClosure() {
// Try to map using the post table. There are currently no known
// pdfs that this fixes.
var glyphId = properties.glyphNames.indexOf(glyphName);
if (glyphId > 0) {
if (glyphId > 0 && hasGlyph(glyphId, -1)) {
charCodeToGlyphId[charCode] = glyphId;
}
}
}
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
// Default Unicode semantics, use the charcodes as is.
for (i = 0; i < cmapMappingsLength; ++i) {
charCodeToGlyphId[cmapMappings[i].charCode] =
cmapMappings[i].glyphId;
}
} else {
// For (3, 0) cmap tables:
// The charcode key being stored in charCodeToGlyphId is the lower
@ -32715,6 +32753,10 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
var gotEOL = false;
if (this.byteAlign) {
this.inputBits &= ~7;
}
if (!this.eoblock && this.row === this.rows - 1) {
this.eof = true;
} else {
@ -32738,10 +32780,6 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
}
}
if (this.byteAlign && !gotEOL) {
this.inputBits &= ~7;
}
if (!this.eof && this.encoding > 0) {
this.nextLine2D = !this.lookBits(1);
this.eatBits(1);

Просмотреть файл

@ -5481,12 +5481,8 @@ var PDFViewerApplication = {
get supportsPrinting() {
var canvas = document.createElement('canvas');
var value = 'mozPrintCallback' in canvas;
// shadow
Object.defineProperty(this, 'supportsPrinting', { value: value,
enumerable: true,
configurable: true,
writable: false });
return value;
return PDFJS.shadow(this, 'supportsPrinting', value);
},
get supportsFullscreen() {
@ -5501,50 +5497,34 @@ var PDFViewerApplication = {
support = false;
}
Object.defineProperty(this, 'supportsFullscreen', { value: support,
enumerable: true,
configurable: true,
writable: false });
return support;
return PDFJS.shadow(this, 'supportsFullscreen', support);
},
get supportsIntegratedFind() {
var support = false;
support = FirefoxCom.requestSync('supportsIntegratedFind');
Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
enumerable: true,
configurable: true,
writable: false });
return support;
return PDFJS.shadow(this, 'supportsIntegratedFind', support);
},
get supportsDocumentFonts() {
var support = true;
support = FirefoxCom.requestSync('supportsDocumentFonts');
Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
enumerable: true,
configurable: true,
writable: false });
return support;
return PDFJS.shadow(this, 'supportsDocumentFonts', support);
},
get supportsDocumentColors() {
var support = true;
support = FirefoxCom.requestSync('supportsDocumentColors');
Object.defineProperty(this, 'supportsDocumentColors', { value: support,
enumerable: true,
configurable: true,
writable: false });
return support;
return PDFJS.shadow(this, 'supportsDocumentColors', support);
},
get loadingBar() {
var bar = new ProgressBar('#loadingBar', {});
Object.defineProperty(this, 'loadingBar', { value: bar,
enumerable: true,
configurable: true,
writable: false });
return bar;
return PDFJS.shadow(this, 'loadingBar', bar);
},
initPassiveLoading: function pdfViewInitPassiveLoading() {

Просмотреть файл

@ -7,6 +7,7 @@
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
## should remain "Firefox Hello" for all locales.
clientShortname2=Firefox Hello
clientSuperShortname=Hello
rooms_tab_button_tooltip=Conversations
contacts_tab_button_tooltip=Contacts
@ -43,10 +44,11 @@ problem_accessing_account=There Was A Problem Accessing Your Account
## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error for location
retry_button=Retry
share_email_subject4={{clientShortname}} — Join the conversation
share_email_subject5={{clientShortname2}} — Join the conversation
## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
## part between {{..}} and leave the \r\n\r\n part alone
share_email_body4=Hello!\r\n\r\nJoin me for a video conversation using {{clientShortname}}:\r\n\r\nYou don't have to download or install anything. Just copy and paste this URL into your browser:\r\n\r\n{{callUrl}}\r\n\r\nIf you want, you can also learn more about {{clientShortname}} at {{learnMoreUrl}}\r\n\r\nTalk to you soon!
## part between {{..}} and leave the \n\n part alone
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
share_button2=Email Link
copy_url_button2=Copy Link
copied_url_button=Copied!
@ -167,6 +169,14 @@ audio_call_menu_button=Audio Conversation
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#contacts
video_call_menu_button=Video Conversation
## LOCALIZATION NOTE(gravatars_promo_message): The {{learn_more}} part will be
## replaced with a link with the text content of gravatars_promo_message_learnmore.
gravatars_promo_message=You can automatically add profile icons to your contacts \
by sharing their email addresses with Gravatar. {{learn_more}}.
gravatars_promo_message_learnmore=Learn more
gravatars_promo_button_nothanks=No Thanks
gravatars_promo_button_use=Use Profile Icons
# Conversation Window Strings
initiate_call_button_label2=Ready to start your conversation?
@ -185,6 +195,7 @@ mute_local_video_button_title=Mute your video
unmute_local_video_button_title=Unmute your video
active_screenshare_button_title=Stop sharing
inactive_screenshare_button_title=Share your screen
share_tabs_button_title=Share my Tabs
share_windows_button_title=Share other Windows
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
@ -312,3 +323,11 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
rooms_room_joined_label=Someone has joined the conversation!
rooms_room_join_label=Join the conversation
rooms_signout_alert=Open conversations will be closed
# Infobar strings
infobar_screenshare_browser_message=Users in your conversation will now be able to see the contents of any tab you click on.
infobar_button_gotit_label=Got it!
infobar_button_gotit_accesskey=G
infobar_menuitem_dontshowagain_label=Don't show this again
infobar_menuitem_dontshowagain_accesskey=D

Просмотреть файл

@ -13,6 +13,10 @@ phishBeforeText=Selecting this option will send the address of web pages you are
labelDefaultFont=Default (%S)
veryLargeMinimumFontTitle=Large minimum font size
veryLargeMinimumFontWarning=You have selected a very large minimum font size (more than 24 pixels). This may make it difficult or impossible to use some important configuration pages like this one.
acceptVeryLargeMinimumFont=Keep my changes anyway
#### Permissions Manager
cookiepermissionstext=You can specify which websites are always or never allowed to use cookies. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.

Просмотреть файл

@ -23,5 +23,12 @@ cmd_clearHistory_accesskey=H
cmd_showSuggestions=Show Suggestions
cmd_showSuggestions_accesskey=S
# LOCALIZATION NOTE (cmd_addFoundEngine): %S is replaced by the name of
# a search engine offered by a web page. Each engine is displayed as a
# menuitem at the bottom of the search panel.
cmd_addFoundEngine=Add "%S"
# LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines
# are offered by a web page, instead of listing all of them in the
# search panel using the cmd_addFoundEngine string, they will be
# grouped in a submenu using cmd_addFoundEngineMenu as a label.
cmd_addFoundEngineMenu=Add search engine

Просмотреть файл

@ -198,7 +198,9 @@ let AboutHome = {
#endif
// Trigger a search through nsISearchEngine.getSubmission()
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
window.loadURI(submission.uri.spec, null, submission.postData);
let where = data.useNewTab ? "tab" : "current";
window.openUILinkIn(submission.uri.spec, where, false,
submission.postData);
// Used for testing
let mm = aMessage.target.messageManager;

Просмотреть файл

@ -599,6 +599,10 @@ this.BrowserUITelemetry = {
this._countEvent(["click-builtin-item", source, "search-settings"]);
},
countPanicEvent: function(timeId) {
this._countEvent(["forget-button", timeId]);
},
_logAwesomeBarSearchResult: function (url) {
let spec = Services.search.parseSubmissionURL(url);
if (spec.engine) {

Просмотреть файл

@ -210,6 +210,11 @@ this.ContentSearch = {
let engine = Services.search.getEngineByName(data.engineName);
let submission = engine.getSubmission(data.searchString, "", data.whence);
let browser = msg.target;
let newTab;
if (data.useNewTab) {
newTab = browser.getTabBrowser().addTab();
browser = newTab.linkedBrowser;
}
try {
browser.loadURIWithFlags(submission.uri.spec,
Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
@ -221,6 +226,9 @@ this.ContentSearch = {
// method on it will throw.
return Promise.resolve();
}
if (data.useNewTab) {
browser.getTabBrowser().selectedTab = newTab;
}
let win = browser.ownerDocument.defaultView;
win.BrowserSearch.recordSearchInHealthReport(engine, data.whence,
data.selection || null);

Просмотреть файл

@ -116,10 +116,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
.search-panel-current-engine {
border-top: none !important;
border-bottom: 1px solid #ccc;
-moz-box-align: center;
}
.search-panel-current-engine:not([showonlysettings]) {
border-bottom: 1px solid #ccc;
}
.search-panel-header {
font-weight: normal;
background-color: rgb(245, 245, 245);

Просмотреть файл

@ -139,10 +139,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
.search-panel-current-engine {
border-top: none !important;
border-bottom: 1px solid #ccc;
border-radius: 4px 4px 0 0;
}
.search-panel-current-engine:not([showonlysettings]) {
border-bottom: 1px solid #ccc;
}
.search-panel-header {
font-size: 10px;
font-weight: normal;
@ -287,10 +290,6 @@ searchbar[oneoffui] .searchbar-engine-button {
min-height: 32px;
}
.search-setting-button[showonlysettings] {
border-radius: 4px;
}
.search-setting-button[selected] {
background-color: #d3d3d3;
border-top-color: #bdbebe;

Просмотреть файл

@ -128,10 +128,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
.search-panel-current-engine {
border-top: none !important;
border-bottom: 1px solid #ccc;
-moz-box-align: center;
}
.search-panel-current-engine:not([showonlysettings]) {
border-bottom: 1px solid #ccc;
}
.search-panel-header {
font-weight: normal;
background-color: rgb(245, 245, 245);

Просмотреть файл

@ -8535,6 +8535,7 @@ AC_SUBST(MOZ_WEBSMS_BACKEND)
AC_SUBST(MOZ_ANDROID_BEAM)
AC_SUBST(MOZ_LOCALE_SWITCHER)
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
AC_SUBST(MOZ_ANDROID_SHARE_OVERLAY)

Просмотреть файл

@ -1,12 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
include $(topsrcdir)/config/rules.mk
PATTERN_FILES = $(strip $(wildcard $(srcdir)/*/hyphenation/*.dic))
ifneq (,$(PATTERN_FILES))
libs::
$(INSTALL) $(PATTERN_FILES) $(FINAL_TARGET)/hyphenation
endif

Просмотреть файл

@ -4,3 +4,45 @@
# 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/.
locales = [
'af',
'bg',
'ca',
'cy',
'da',
'de-1901',
'de-1996',
'de-CH',
# 'en-US', # en-US is renamed -- see below.
'eo',
'es',
'et',
'fi',
'fr',
'gl',
'hr',
'hsb',
'hu',
'ia',
'is',
'it',
'kmr',
'la',
'lt',
'mn',
'nb',
'nl',
'nn',
'pl',
'pt',
'ru',
'sh',
'sl',
'sv',
'tr',
'uk',
]
filename = '{locale}/hyphenation/hyph_{locale}.dic'
FINAL_TARGET_FILES.hyphenation += [filename.format(locale=locale) for locale in locales]
# en-US is a special case: the dic file is named like en_US.
FINAL_TARGET_FILES.hyphenation += ['en-US/hyphenation/hyph_en_US.dic']

Просмотреть файл

@ -860,11 +860,5 @@ pref("reader.color_scheme", "auto");
// Color scheme values available in reader mode UI.
pref("reader.color_scheme.values", "[\"light\",\"dark\",\"auto\"]");
// The font type in reader (charis-sil, clear-sans)
pref("reader.font_type", "clear-sans");
// Font type values available in reader mode UI.
pref("reader.font_type.values", "[\"charis-sil\",\"clear-sans\"]");
// Whether to use a vertical or horizontal toolbar.
pref("reader.toolbar.vertical", false);

Просмотреть файл

@ -154,6 +154,8 @@ public class BrowserApp extends GeckoApp
LayoutInflater.Factory {
private static final String LOGTAG = "GeckoBrowserApp";
private static final boolean ZOOMED_VIEW_ENABLED = AppConstants.NIGHTLY_BUILD;
private static final int TABS_ANIMATION_DURATION = 450;
private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
@ -181,10 +183,10 @@ public class BrowserApp extends GeckoApp
private HomePager mHomePager;
private TabsPanel mTabsPanel;
private ViewGroup mHomePagerContainer;
protected Telemetry.Timer mAboutHomeStartupTimer;
private ActionModeCompat mActionMode;
private boolean mHideDynamicToolbarOnActionModeEnd;
private TabHistoryController tabHistoryController;
private ZoomedView mZoomedView;
private static final int GECKO_TOOLS_MENU = -1;
private static final int ADDON_MENU_OFFSET = 1000;
@ -657,8 +659,6 @@ public class BrowserApp extends GeckoApp
@Override
public void onCreate(Bundle savedInstanceState) {
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
final Intent intent = getIntent();
// Note that we're calling GeckoProfile.get *before GeckoApp.onCreate*.
@ -827,6 +827,11 @@ public class BrowserApp extends GeckoApp
// Set the maximum bits-per-pixel the favicon system cares about.
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
if (ZOOMED_VIEW_ENABLED) {
ViewStub stub = (ViewStub) findViewById(R.id.zoomed_view_stub);
mZoomedView = (ZoomedView) stub.inflate();
}
}
private void setupSystemUITinting() {
@ -1301,6 +1306,9 @@ public class BrowserApp extends GeckoApp
mReadingListHelper.uninit();
mReadingListHelper = null;
}
if (mZoomedView != null) {
mZoomedView.destroy();
}
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
"Menu:Open",
@ -1378,16 +1386,6 @@ public class BrowserApp extends GeckoApp
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
}
@Override
protected void loadStartupTab(String url, int flags) {
// We aren't showing about:home, so cancel the telemetry timer
if (url != null || mShouldRestore) {
mAboutHomeStartupTimer.cancel();
}
super.loadStartupTab(url, flags);
}
private void setToolbarMargin(int margin) {
((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
mGeckoLayout.requestLayout();

Просмотреть файл

@ -129,8 +129,6 @@ public abstract class GeckoApp
private static final String LOGTAG = "GeckoApp";
private static final int ONE_DAY_MS = 1000*60*60*24;
private static final boolean ZOOMED_VIEW_ENABLED = AppConstants.NIGHTLY_BUILD;
private static enum StartupAction {
NORMAL, /* normal application start */
URL, /* launched with a passed URL */
@ -175,7 +173,6 @@ public abstract class GeckoApp
private ContactService mContactService;
private PromptService mPromptService;
private TextSelection mTextSelection;
private ZoomedView mZoomedView;
protected DoorHangerPopup mDoorHangerPopup;
protected FormAssistPopup mFormAssistPopup;
@ -1578,11 +1575,6 @@ public abstract class GeckoApp
(TextSelectionHandle) findViewById(R.id.caret_handle),
(TextSelectionHandle) findViewById(R.id.focus_handle));
if (ZOOMED_VIEW_ENABLED) {
ViewStub stub = (ViewStub) findViewById(R.id.zoomed_view_stub);
mZoomedView = (ZoomedView) stub.inflate();
}
// Trigger the completion of the telemetry timer that wraps activity startup,
// then grab the duration to give to FHR.
mJavaUiStartupTimer.stop();
@ -2049,9 +2041,6 @@ public abstract class GeckoApp
mPromptService.destroy();
if (mTextSelection != null)
mTextSelection.destroy();
if (mZoomedView != null) {
mZoomedView.destroy();
}
NotificationHelper.destroy();
IntentHelper.destroy();
GeckoNetworkManager.destroy();

Просмотреть файл

@ -179,6 +179,10 @@ public class Tab {
return mUrl;
}
/**
* Returns the base domain of the loaded uri. Note that if the page is
* a Reader mode uri, the base domain returned is that of the original uri.
*/
public String getBaseDomain() {
return mBaseDomain;
}

Просмотреть файл

@ -4,7 +4,11 @@
package org.mozilla.gecko.db;
import android.annotation.TargetApi;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.Build;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
@ -15,6 +19,9 @@ import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.mozglue.RobocopTarget;
import java.util.Map;
public class DBUtils {
private static final String LOGTAG = "GeckoDBUtils";
@ -173,4 +180,175 @@ public class DBUtils {
}
return appendProfile(profile, uri);
}
/**
* Use the following when no conflict action is specified.
*/
private static final int CONFLICT_NONE = 0;
private static final String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
/**
* Convenience method for updating rows in the database.
*
* @param table the table to update in
* @param values a map from column names to new column values. null is a
* valid value that will be translated to NULL.
* @param whereClause the optional WHERE clause to apply when updating.
* Passing null will update all rows.
* @param whereArgs You may include ?s in the where clause, which
* will be replaced by the values from whereArgs. The values
* will be bound as Strings.
* @return the number of rows affected
*/
@RobocopTarget
public static int updateArrays(SQLiteDatabase db, String table, ContentValues[] values, UpdateOperation[] ops, String whereClause, String[] whereArgs) {
return updateArraysWithOnConflict(db, table, values, ops, whereClause, whereArgs, CONFLICT_NONE, true);
}
public static void updateArraysBlindly(SQLiteDatabase db, String table, ContentValues[] values, UpdateOperation[] ops, String whereClause, String[] whereArgs) {
updateArraysWithOnConflict(db, table, values, ops, whereClause, whereArgs, CONFLICT_NONE, false);
}
@RobocopTarget
public enum UpdateOperation {
ASSIGN,
BITWISE_OR,
}
/**
* This is an evil reimplementation of SQLiteDatabase's methods to allow for
* smarter updating.
*
* Each ContentValues has an associated enum that describes how to unify input values with the existing column values.
*/
private static int updateArraysWithOnConflict(SQLiteDatabase db, String table,
ContentValues[] values,
UpdateOperation[] ops,
String whereClause,
String[] whereArgs,
int conflictAlgorithm,
boolean returnChangedRows) {
if (values == null || values.length == 0) {
throw new IllegalArgumentException("Empty values");
}
if (ops == null || ops.length != values.length) {
throw new IllegalArgumentException("ops and values don't match");
}
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
// move all bind args to one array
int setValuesSize = 0;
for (int i = 0; i < values.length; i++) {
setValuesSize += values[i].size();
}
int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
Object[] bindArgs = new Object[bindArgsSize];
int arg = 0;
for (int i = 0; i < values.length; i++) {
final ContentValues v = values[i];
final UpdateOperation op = ops[i];
// Alas, code duplication.
switch (op) {
case ASSIGN:
for (Map.Entry<String, Object> entry : v.valueSet()) {
final String colName = entry.getKey();
sql.append((arg > 0) ? "," : "");
sql.append(colName);
bindArgs[arg++] = entry.getValue();
sql.append("= ?");
}
break;
case BITWISE_OR:
for (Map.Entry<String, Object> entry : v.valueSet()) {
final String colName = entry.getKey();
sql.append((arg > 0) ? "," : "");
sql.append(colName);
bindArgs[arg++] = entry.getValue();
sql.append("= ? | ");
sql.append(colName);
}
break;
}
}
if (whereArgs != null) {
for (arg = setValuesSize; arg < bindArgsSize; arg++) {
bindArgs[arg] = whereArgs[arg - setValuesSize];
}
}
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE ");
sql.append(whereClause);
}
// What a huge pain in the ass, all because SQLiteDatabase doesn't expose .executeSql,
// and we can't get a DB handle. Nor can we easily construct a statement with arguments
// already bound.
final SQLiteStatement statement = db.compileStatement(sql.toString());
try {
bindAllArgs(statement, bindArgs);
if (!returnChangedRows) {
statement.execute();
return 0;
}
if (AppConstants.Versions.feature11Plus) {
// This is a separate method so we can annotate it with @TargetApi.
return executeStatementReturningChangedRows(statement);
} else {
statement.execute();
final Cursor cursor = db.rawQuery("SELECT changes()", null);
try {
cursor.moveToFirst();
return cursor.getInt(0);
} finally {
cursor.close();
}
}
} finally {
statement.close();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static int executeStatementReturningChangedRows(SQLiteStatement statement) {
return statement.executeUpdateDelete();
}
// All because {@link SQLiteProgram#bind(integer, Object)} is private.
private static void bindAllArgs(SQLiteStatement statement, Object[] bindArgs) {
if (bindArgs == null) {
return;
}
for (int i = bindArgs.length; i != 0; i--) {
Object v = bindArgs[i - 1];
if (v == null) {
statement.bindNull(i);
} else if (v instanceof String) {
statement.bindString(i, (String) v);
} else if (v instanceof Double) {
statement.bindDouble(i, (Double) v);
} else if (v instanceof Float) {
statement.bindDouble(i, (Float) v);
} else if (v instanceof Long) {
statement.bindLong(i, (Long) v);
} else if (v instanceof Integer) {
statement.bindLong(i, (Integer) v);
} else if (v instanceof Byte) {
statement.bindLong(i, (Byte) v);
} else if (v instanceof byte[]) {
statement.bindBlob(i, (byte[]) v);
}
}
}
}

Просмотреть файл

@ -24,6 +24,19 @@ public class PerProfileDatabases<T extends SQLiteOpenHelper> {
private final String mDatabaseName;
private final DatabaseHelperFactory<T> mHelperFactory;
// Only used during tests.
public void shutdown() {
synchronized (this) {
for (T t : mStorages.values()) {
try {
t.close();
} catch (Throwable e) {
// Never mind.
}
}
}
}
public interface DatabaseHelperFactory<T> {
public T makeDatabaseHelper(Context context, String databasePath);
}

Просмотреть файл

@ -38,6 +38,14 @@ public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDa
return databases;
}
// Can't mark as @Override. Added in API 11.
public void shutdown() {
synchronized (SharedBrowserDatabaseProvider.class) {
databases.shutdown();
databases = null;
}
}
@Override
public boolean onCreate() {
// If necessary, do the shared DB work.
@ -95,8 +103,8 @@ public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDa
// IDs of matching rows, then delete them in one go.
final long now = System.currentTimeMillis();
final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
SyncColumns.DATE_MODIFIED + " <= " +
(now - MAX_AGE_OF_DELETED_RECORDS);
SyncColumns.DATE_MODIFIED + " <= " +
(now - MAX_AGE_OF_DELETED_RECORDS);
final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));

Просмотреть файл

@ -376,6 +376,9 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
@Override
public void onAnimationEnd(Animation animation) {
// (bug 1132720) Hide the View so it doesn't flicker as the Activity closes.
ShareDialog.this.setVisible(false);
finish();
}

Просмотреть файл

@ -33,8 +33,11 @@ skip-if = android_version == "10"
[testClearPrivateData]
# disabled on x86 and 2.3; bug 948591
skip-if = android_version == "10" || processor == "x86"
[testDBUtils]
[testDistribution]
[testDoorHanger]
# disabled on 2.3; bug 1085609
skip-if = android_version == "10"
[testFilterOpenTab]
[testFindInPage]
# disabled on 2.3

Просмотреть файл

@ -0,0 +1,70 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tests;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.mozilla.gecko.db.DBUtils;
import java.io.File;
import java.io.IOException;
public class testDBUtils extends BaseTest {
public void testDBUtils() throws IOException {
final File cacheDir = getInstrumentation().getContext().getCacheDir();
final File dbFile = File.createTempFile("testDBUtils", ".db", cacheDir);
final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
try {
mAsserter.ok(db != null, "Created DB.", null);
db.execSQL("CREATE TABLE foo (x INTEGER NOT NULL DEFAULT 0, y TEXT)");
final ContentValues v = new ContentValues();
v.put("x", 5);
v.put("y", "a");
db.insert("foo", null, v);
v.put("x", 2);
v.putNull("y");
db.insert("foo", null, v);
v.put("x", 3);
v.put("y", "z");
db.insert("foo", null, v);
DBUtils.UpdateOperation[] ops = {DBUtils.UpdateOperation.BITWISE_OR, DBUtils.UpdateOperation.ASSIGN};
ContentValues[] values = {new ContentValues(), new ContentValues()};
values[0].put("x", 0xff);
values[1].put("y", "hello");
final int updated = DBUtils.updateArrays(db, "foo", values, ops, "x >= 3", null);
mAsserter.ok(updated == 2, "Updated two rows.", null);
final Cursor out = db.query("foo", new String[]{"x", "y"}, null, null, null, null, "x");
try {
mAsserter.ok(out.moveToNext(), "Has first result.", null);
mAsserter.ok(2 == out.getInt(0), "1: First column was untouched.", null);
mAsserter.ok(out.isNull(1), "1: Second column was untouched.", null);
mAsserter.ok(out.moveToNext(), "Has second result.", null);
mAsserter.ok((0xff | 3) == out.getInt(0), "2: First column was ORed correctly.", null);
mAsserter.ok("hello".equals(out.getString(1)), "2: Second column was assigned correctly.", null);
mAsserter.ok(out.moveToNext(), "Has third result.", null);
mAsserter.ok((0xff | 5) == out.getInt(0), "3: First column was ORed correctly.", null);
mAsserter.ok("hello".equals(out.getString(1)), "3: Second column was assigned correctly.", null);
mAsserter.ok(!out.moveToNext(), "No more results.", null);
} finally {
out.close();
}
} finally {
try {
db.close();
} catch (Exception e) {
}
dbFile.delete();
}
}
}

Просмотреть файл

@ -14,6 +14,7 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ReaderModeUtils;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.SiteIdentity.MixedMode;
@ -191,7 +192,7 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
mSiteIdentityPopup.setAnchor(mTitle);
mSiteIdentityPopup.setAnchor(mSiteSecurity);
mStop = (ImageButton) findViewById(R.id.stop);
mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
@ -360,11 +361,14 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
return;
}
CharSequence title = url;
String strippedURL = stripAboutReaderURL(url);
if (mPrefs.shouldTrimUrls()) {
title = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url));
strippedURL = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(strippedURL));
}
CharSequence title = strippedURL;
final String baseDomain = tab.getBaseDomain();
if (!TextUtils.isEmpty(baseDomain)) {
final SpannableStringBuilder builder = new SpannableStringBuilder(title);
@ -382,6 +386,14 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
setTitle(title);
}
private String stripAboutReaderURL(final String url) {
if (!AboutPages.isAboutReader(url)) {
return url;
}
return ReaderModeUtils.getUrlFromAboutReader(url);
}
private void updateFavicon(Tab tab) {
if (HardwareUtils.isTablet()) {
// We don't display favicons in the toolbar on tablet.

Просмотреть файл

@ -4430,7 +4430,11 @@ Tab.prototype = {
this.clickToPlayPluginsActivated = false;
// Borrowed from desktop Firefox: http://mxr.mozilla.org/mozilla-central/source/browser/base/content/urlbarBindings.xml#174
let documentURI = contentWin.document.documentURIObject.spec
let matchedURL = documentURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
// If reader mode, get the base domain for the original url.
let strippedURI = this._stripAboutReaderURL(documentURI);
let matchedURL = strippedURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
let baseDomain = "";
if (matchedURL) {
var domain = "";
@ -4488,6 +4492,19 @@ Tab.prototype = {
}
},
_stripAboutReaderURL: function (url) {
if (!url.startsWith("about:reader")) {
return url;
}
// From ReaderParent._getOriginalUrl (browser/modules/ReaderParent.jsm).
let searchParams = new URLSearchParams(url.substring("about:reader?".length));
if (!searchParams.has("url")) {
return url;
}
return decodeURIComponent(searchParams.get("url"));
},
// Properties used to cache security state used to update the UI
_state: null,
_hostChanged: false, // onLocationChange will flip this bit

Просмотреть файл

@ -15,4 +15,6 @@ STRIP_FLAGS="--strip-debug"
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
MOZ_ANDROID_GECKOLIBS_AAR=1
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"

Просмотреть файл

@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.gecko.libs">
</manifest>

Двоичные данные
mobile/android/geckoview_library/geckolibs/classes.jar Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -25,11 +25,11 @@ body {
color: #eeeeee;
}
.clear-sans {
.sans-serif {
font-family: sans-serif;
}
.charis-sil {
.serif {
font-family: serif;
}

Просмотреть файл

@ -4555,9 +4555,6 @@ pref("reader.color_scheme.values", "[\"light\",\"dark\",\"sepia\"]");
// The font type in reader (sans-serif, serif)
pref("reader.font_type", "sans-serif");
// Font type values available in reader mode UI.
pref("reader.font_type.values", "[\"serif\",\"sans-serif\"]");
// Whether or not the user has interacted with the reader mode toolbar.
// This is used to show a first-launch tip in reader mode.
pref("reader.has_used_toolbar", false);

Просмотреть файл

@ -0,0 +1,131 @@
# 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/.
'''
Script to produce an Android ARchive (.aar) containing the compiled
Gecko library binaries. The AAR file is intended for use by local
developers using Gradle.
'''
from __future__ import print_function
import argparse
import hashlib
import os
import sys
from mozbuild import util
from mozpack.copier import Jarrer
from mozpack.files import (
File,
FileFinder,
)
MAVEN_POM_TEMPLATE = r'''
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>{groupId}</groupId>
<artifactId>{artifactId}</artifactId>
<version>{version}</version>
<packaging>{packaging}</packaging>
</project>
'''.lstrip()
IVY_XML_TEMPLATE = r'''
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0">
<info organisation="{organisation}" module="{module}" revision="{revision}" status="integration" publication="{publication}"/>
<configurations/>
<publications>
<artifact name="{name}" type="{type}" ext="{ext}"/>
</publications>
<dependencies/>
</ivy-module>
'''.lstrip()
def package_geckolibs_aar(topsrcdir, distdir, output_file):
jarrer = Jarrer(optimize=False)
srcdir = os.path.join(topsrcdir, 'mobile', 'android', 'geckoview_library', 'geckolibs')
jarrer.add('AndroidManifest.xml', File(os.path.join(srcdir, 'AndroidManifest.xml')))
jarrer.add('classes.jar', File(os.path.join(srcdir, 'classes.jar')))
jni = FileFinder(os.path.join(distdir, 'fennec', 'lib'))
for p, f in jni.find('**/*.so'):
jarrer.add(os.path.join('jni', p), f)
# Include the buildinfo JSON as an asset, to give future consumers at least
# a hope of determining where this AAR came from.
json = FileFinder(distdir, ignore=['*.mozinfo.json'])
for p, f in json.find('*.json'):
jarrer.add(os.path.join('assets', p), f)
# This neatly ignores omni.ja.
assets = FileFinder(os.path.join(distdir, 'fennec', 'assets'))
for p, f in assets.find('**/*.so'):
jarrer.add(os.path.join('assets', p), f)
jarrer.copy(output_file)
return 0
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument(dest='dir',
metavar='DIR',
help='Path to write geckolibs Android ARchive and metadata to.')
parser.add_argument('--verbose', '-v', default=False, action='store_true',
help='be verbose')
parser.add_argument('--revision',
help='Revision identifier to write.')
parser.add_argument('--topsrcdir',
help='Top source directory.')
parser.add_argument('--distdir',
help='Distribution directory (usually $OBJDIR/dist).')
args = parser.parse_args(args)
paths_to_hash = []
aar = os.path.join(args.dir, 'geckolibs-{revision}.aar').format(revision=args.revision)
paths_to_hash.append(aar)
package_geckolibs_aar(args.topsrcdir, args.distdir, aar)
pom = os.path.join(args.dir, 'geckolibs-{revision}.pom').format(revision=args.revision)
paths_to_hash.append(pom)
with open(pom, 'wt') as f:
f.write(MAVEN_POM_TEMPLATE.format(
groupId='org.mozilla',
artifactId='geckolibs',
version=args.revision,
packaging='aar',
))
ivy = os.path.join(args.dir, 'ivy-geckolibs-{revision}.xml').format(revision=args.revision)
paths_to_hash.append(ivy)
with open(ivy, 'wt') as f:
f.write(IVY_XML_TEMPLATE.format(
organisation='org.mozilla',
module='geckolibs',
revision=args.revision,
publication=args.revision, # A white lie.
name='geckolibs',
type='aar',
ext='aar',
))
for p in paths_to_hash:
sha = "%s.sha1" % p
with open(sha, 'wt') as f:
f.write(util.hash_file(p, hasher=hashlib.sha1()))
if args.verbose:
print(p)
print(sha)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

Просмотреть файл

@ -31,12 +31,12 @@ if sys.version_info[0] == 3:
else:
str_type = basestring
def hash_file(path):
def hash_file(path, hasher=None):
"""Hashes a file specified by the path given and returns the hex digest."""
# If the hashing function changes, this may invalidate lots of cached data.
# Don't change it lightly.
h = hashlib.sha1()
# If the default hashing function changes, this may invalidate
# lots of cached data. Don't change it lightly.
h = hasher or hashlib.sha1()
with open(path, 'rb') as fh:
while True:

Просмотреть файл

@ -73,13 +73,17 @@ let AboutReader = function(mm, win) {
this._setColorSchemePref(colorScheme);
let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
let fontTypeValues = JSON.parse(Services.prefs.getCharPref("reader.font_type.values"));
let fontTypeOptions = fontTypeValues.map((value) => {
return { name: fontTypeSample,
description: gStrings.GetStringFromName("aboutReader.fontType." + value),
value: value,
linkClass: value };
});
let fontTypeOptions = [
{ name: fontTypeSample,
description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
value: "serif",
linkClass: "serif" },
{ name: fontTypeSample,
description: gStrings.GetStringFromName("aboutReader.fontType.sans-serif"),
value: "sans-serif",
linkClass: "sans-serif"
},
];
let fontType = Services.prefs.getCharPref("reader.font_type");
this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));

Просмотреть файл

@ -4532,15 +4532,6 @@
"description": "The way the GeckoApp was launched. (Normal, URL, Prefetch, Redirector)",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_TIME_ABOUTHOME": {
"expires_in_version": "never",
"kind": "exponential",
"low": 100,
"high": "10000",
"n_buckets": 20,
"description": "Time for the about:home page to be displayed (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_TIME_GECKOREADY": {
"expires_in_version": "never",
"kind": "exponential",

Просмотреть файл

@ -11,15 +11,10 @@ aboutReader.colorScheme.sepia=Sepia
aboutReader.colorScheme.auto=Auto
# LOCALIZATION NOTE (aboutReader.fontType.serif, aboutReader.fontType.sans-serif):
# These are the styles of typeface that are used on desktop.
# These are the styles of typeface that are options in the reader view controls.
aboutReader.fontType.serif=Serif
aboutReader.fontType.sans-serif=Sans-serif
# LOCALIZATION NOTE (aboutReader.fontType.charis-sil, aboutReader.fontType.clear-sans):
# These are the names of the fonts that are used on Android
aboutReader.fontType.charis-sil=Charis SIL Compact
aboutReader.fontType.clear-sans=Clear Sans
# LOCALIZATION NOTE (aboutReader.fontTypeSample): String used to sample font types.
aboutReader.fontTypeSample=Aa

Просмотреть файл

@ -362,6 +362,31 @@ INNER_MAKE_GECKOVIEW_LIBRARY=echo 'GeckoView library packaging is only enabled o
INNER_MAKE_GECKOVIEW_EXAMPLE=echo 'GeckoView example packaging is only enabled on Nightly'
endif
# Create geckolibs Android ARchive and metadata for download by local
# developers using Gradle.
ifdef MOZ_ANDROID_GECKOLIBS_AAR
geckolibs-revision := $(BUILDID)
UPLOAD_EXTRA_FILES += \
geckolibs-$(geckolibs-revision).aar \
geckolibs-$(geckolibs-revision).aar.sha1 \
geckolibs-$(geckolibs-revision).pom \
geckolibs-$(geckolibs-revision).pom.sha1 \
ivy-geckolibs-$(geckolibs-revision).xml \
ivy-geckolibs-$(geckolibs-revision).xml.sha1 \
$(NULL)
INNER_MAKE_GECKOLIBS_AAR= \
$(PYTHON) -m mozbuild.action.package_geckolibs_aar \
--verbose \
--revision $(geckolibs-revision) \
--topsrcdir '$(topsrcdir)' \
--distdir '$(_ABS_DIST)' \
'$(_ABS_DIST)'
else
INNER_MAKE_GECKOLIBS_AAR=echo 'Android geckolibs.aar packaging is disabled'
endif
ifdef MOZ_OMX_PLUGIN
DIST_FILES += libomxplugin.so libomxplugingb.so libomxplugingb235.so \
libomxpluginhc.so libomxpluginkk.so
@ -438,6 +463,7 @@ INNER_MAKE_PACKAGE = \
$(RELEASE_JARSIGNER) $(_ABS_DIST)/gecko.apk && \
$(ZIPALIGN) -f -v 4 $(_ABS_DIST)/gecko.apk $(PACKAGE) && \
$(INNER_ROBOCOP_PACKAGE) && \
$(INNER_MAKE_GECKOLIBS_AAR) && \
$(INNER_MAKE_GECKOVIEW_LIBRARY) && \
$(INNER_MAKE_GECKOVIEW_EXAMPLE)

Просмотреть файл

@ -71,12 +71,17 @@ function moveToAlertPosition()
sizeToContent();
}
var xOffset = (opener.outerWidth - window.outerWidth) / 2;
var yOffset = opener.outerHeight / 5;
var newX = opener.screenX + xOffset;
var newY = opener.screenY + yOffset;
if (opener) {
var xOffset = (opener.outerWidth - window.outerWidth) / 2;
var yOffset = opener.outerHeight / 5;
var newX = opener.screenX + xOffset;
var newY = opener.screenY + yOffset;
} else {
newX = (screen.availWidth - window.outerWidth) / 2;
newY = (screen.availHeight - window.outerHeight) / 2;
}
// ensure the window is fully onscreen (if smaller than the screen)
if (newX < screen.availLeft)
newX = screen.availLeft + 20;