зеркало из https://github.com/mozilla/gecko-dev.git
Bug 750903 - Land browser/metro/* r=mbrubeck
This commit is contained in:
Родитель
006d130536
Коммит
b3a0b467c6
|
@ -0,0 +1,51 @@
|
|||
# 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/.
|
||||
|
||||
DEPTH = ../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
||||
DIRS = \
|
||||
base \
|
||||
components \
|
||||
modules \
|
||||
theme \
|
||||
profile \
|
||||
locales \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
DIRS += shell
|
||||
endif
|
||||
|
||||
TEST_DIRS += base/tests
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
#########################################
|
||||
# application.ini
|
||||
|
||||
ifdef MOZILLA_OFFICIAL
|
||||
DEFINES += -DMOZILLA_OFFICIAL
|
||||
endif
|
||||
|
||||
GRE_MILESTONE := $(shell tail -n 1 $(topsrcdir)/config/milestone.txt 2>/dev/null || tail -1 $(topsrcdir)/config/milestone.txt)
|
||||
GRE_BUILDID := $(shell cat $(DEPTH)/config/buildid)
|
||||
DEFINES += -DGRE_MILESTONE=$(GRE_MILESTONE) -DGRE_BUILDID=$(GRE_BUILDID)
|
||||
|
||||
# 'application.ini' breaks firefox build config. So we use something different.
|
||||
metroapp.ini: metroapp.ini.in $(DEPTH)/config/buildid $(topsrcdir)/config/milestone.txt
|
||||
$(RM) "metroapp.ini"
|
||||
$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $@
|
||||
|
||||
libs:: metroapp.ini
|
||||
$(INSTALL) metroapp.ini $(FINAL_TARGET)
|
||||
|
||||
#########################################
|
||||
|
||||
GARBAGE += metroapp.ini
|
|
@ -0,0 +1,20 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH = ../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
||||
DIST_SUBDIR=metro
|
||||
|
||||
DEFINES += -DAB_CD=$(MOZ_UI_LOCALE) \
|
||||
-DPACKAGE=browser \
|
||||
-DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
|
@ -0,0 +1,124 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Responsible for zooming in to a given view rectangle
|
||||
*/
|
||||
const AnimatedZoom = {
|
||||
startScale: null,
|
||||
|
||||
/** Starts an animated zoom to zoomRect. */
|
||||
animateTo: function(aZoomRect) {
|
||||
if (!aZoomRect)
|
||||
return;
|
||||
|
||||
this.zoomTo = aZoomRect.clone();
|
||||
|
||||
if (this.animationDuration === undefined)
|
||||
this.animationDuration = Services.prefs.getIntPref("browser.ui.zoom.animationDuration");
|
||||
|
||||
Browser.forceChromeReflow();
|
||||
|
||||
this.start();
|
||||
|
||||
// Check if zooming animations were occuring before.
|
||||
if (!this.zoomRect) {
|
||||
this.updateTo(this.zoomFrom);
|
||||
|
||||
mozRequestAnimationFrame(this);
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("AnimatedZoomBegin", true, true);
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
start: function start() {
|
||||
this.tab = Browser.selectedTab;
|
||||
this.browser = this.tab.browser;
|
||||
this.bcr = this.browser.getBoundingClientRect();
|
||||
this.zoomFrom = this.zoomRect || this.getStartRect();
|
||||
this.startScale = this.browser.scale;
|
||||
this.beginTime = mozAnimationStartTime;
|
||||
},
|
||||
|
||||
/** Get the visible rect, in device pixels relative to the content origin. */
|
||||
getStartRect: function getStartRect() {
|
||||
let browser = this.browser;
|
||||
let scroll = browser.getRootView().getPosition();
|
||||
return new Rect(scroll.x, scroll.y, this.bcr.width, this.bcr.height);
|
||||
},
|
||||
|
||||
/** Update the visible rect, in device pixels relative to the content origin. */
|
||||
updateTo: function(nextRect) {
|
||||
// Stop animating if the browser has been destroyed
|
||||
if (typeof this.browser.fuzzyZoom !== "function") {
|
||||
this.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
let zoomRatio = this.bcr.width / nextRect.width;
|
||||
let scale = this.startScale * zoomRatio;
|
||||
let scrollX = nextRect.left * zoomRatio;
|
||||
let scrollY = nextRect.top * zoomRatio;
|
||||
|
||||
this.browser.fuzzyZoom(scale, scrollX, scrollY);
|
||||
|
||||
this.zoomRect = nextRect;
|
||||
return true;
|
||||
},
|
||||
|
||||
/** Stop animation, zoom to point, and clean up. */
|
||||
finish: function() {
|
||||
if (!this.updateTo(this.zoomTo || this.zoomRect))
|
||||
return;
|
||||
|
||||
// Check whether the zoom limits have changed since the animation started.
|
||||
let browser = this.browser;
|
||||
let finalScale = this.tab.clampZoomLevel(browser.scale);
|
||||
if (browser.scale != finalScale)
|
||||
browser.scale = finalScale; // scale= calls finishFuzzyZoom.
|
||||
else
|
||||
browser.finishFuzzyZoom();
|
||||
|
||||
this.reset();
|
||||
browser._updateCSSViewport();
|
||||
},
|
||||
|
||||
reset: function reset() {
|
||||
this.beginTime = null;
|
||||
this.zoomTo = null;
|
||||
this.zoomFrom = null;
|
||||
this.zoomRect = null;
|
||||
this.startScale = null;
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("AnimatedZoomEnd", true, true);
|
||||
window.dispatchEvent(event);
|
||||
},
|
||||
|
||||
isZooming: function isZooming() {
|
||||
return this.beginTime != null;
|
||||
},
|
||||
|
||||
sample: function(aTimeStamp) {
|
||||
try {
|
||||
let tdiff = aTimeStamp - this.beginTime;
|
||||
let counter = tdiff / this.animationDuration;
|
||||
if (counter < 1) {
|
||||
// update browser to interpolated rectangle
|
||||
let rect = this.zoomFrom.blend(this.zoomTo, counter);
|
||||
if (this.updateTo(rect))
|
||||
mozRequestAnimationFrame(this);
|
||||
} else {
|
||||
// last cycle already rendered final scaled image, now clean up
|
||||
this.finish();
|
||||
}
|
||||
} catch(e) {
|
||||
this.finish();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* BrowserTouchHandler
|
||||
*
|
||||
* Receives touch events from our input event capturing in input.js
|
||||
* and relays appropriate events to content. Also handles requests
|
||||
* from content for UI.
|
||||
*/
|
||||
|
||||
const BrowserTouchHandler = {
|
||||
_debugEvents: false,
|
||||
|
||||
init: function init() {
|
||||
// Misc. events we catch during the bubbling phase
|
||||
document.addEventListener("PopupChanged", this, false);
|
||||
document.addEventListener("CancelTouchSequence", this, false);
|
||||
|
||||
// Messages sent from content.js
|
||||
messageManager.addMessageListener("Content:ContextMenu", this);
|
||||
},
|
||||
|
||||
// Content forwarding the contextmenu command
|
||||
onContentContextMenu: function onContentContextMenu(aMessage) {
|
||||
let contextInfo = { name: aMessage.name,
|
||||
json: aMessage.json,
|
||||
target: aMessage.target };
|
||||
// Touch input selection handling
|
||||
if (!InputSourceHelper.isPrecise) {
|
||||
if (SelectionHelperUI.isActive()) {
|
||||
// Selection handler is active.
|
||||
if (aMessage.json.types.indexOf("selected-text") != -1) {
|
||||
// long tap on existing selection. The incoming message has the
|
||||
// string data, so reset the selection handler and invoke the
|
||||
// context menu.
|
||||
SelectionHelperUI.closeEditSession();
|
||||
} else {
|
||||
// Weird, context menu request with no selected text and
|
||||
// SelectionHelperUI is active? Might be a bug, warn. Fall
|
||||
// through anyway, the context menu handler will look in the
|
||||
// incoming message for content types it knows how to handle.
|
||||
Util.dumpLn("long tap on empty selection with SelectionHelperUI active?");
|
||||
SelectionHelperUI.closeEditSession();
|
||||
}
|
||||
} else if (SelectionHelperUI.canHandle(aMessage)) {
|
||||
SelectionHelperUI.openEditSession(aMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we have context menu item(s) that apply to what
|
||||
// was clicked on.
|
||||
if (ContextMenuUI.showContextMenu(contextInfo)) {
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("CancelTouchSequence", true, false);
|
||||
document.dispatchEvent(event);
|
||||
} else {
|
||||
// Send the MozEdgeUIGesture to input.js to
|
||||
// toggle the context ui.
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("MozEdgeUIGesture", true, false);
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
// ignore content events we generate
|
||||
if (this._debugEvents)
|
||||
Util.dumpLn("BrowserTouchHandler:", aEvent.type);
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "PopupChanged":
|
||||
case "CancelTouchSequence":
|
||||
if (!aEvent.detail)
|
||||
ContextMenuUI.reset();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
if (this._debugEvents) Util.dumpLn("BrowserTouchHandler:", aMessage.name);
|
||||
switch (aMessage.name) {
|
||||
// Content forwarding the contextmenu command
|
||||
case "Content:ContextMenu":
|
||||
this.onContentContextMenu(aMessage);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
|
@ -0,0 +1,301 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* context menu command handlers
|
||||
*/
|
||||
|
||||
var ContextCommands = {
|
||||
_picker: null,
|
||||
|
||||
get clipboard() {
|
||||
return Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper);
|
||||
},
|
||||
|
||||
get docRef() {
|
||||
return Browser.selectedBrowser.contentWindow.document;
|
||||
},
|
||||
|
||||
/*
|
||||
* Context menu handlers
|
||||
*/
|
||||
|
||||
copy: function cc_copy() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
if (target.localName == "browser") {
|
||||
if (ContextMenuUI.popupState.string != "undefined") {
|
||||
this.clipboard.copyString(ContextMenuUI.popupState.string,
|
||||
this.docRef);
|
||||
} else {
|
||||
let x = ContextMenuUI.popupState.x;
|
||||
let y = ContextMenuUI.popupState.y;
|
||||
let json = {x: x, y: y, command: "copy" };
|
||||
target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
|
||||
}
|
||||
} else {
|
||||
target.editor.copy();
|
||||
}
|
||||
|
||||
if (target)
|
||||
target.focus();
|
||||
},
|
||||
|
||||
paste: function cc_paste() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
if (target.localName == "browser") {
|
||||
let x = ContextMenuUI.popupState.x;
|
||||
let y = ContextMenuUI.popupState.y;
|
||||
let json = {x: x, y: y, command: "paste" };
|
||||
target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
|
||||
} else {
|
||||
target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
|
||||
target.focus();
|
||||
}
|
||||
},
|
||||
|
||||
pasteAndGo: function cc_pasteAndGo() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
target.editor.selectAll();
|
||||
target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
|
||||
BrowserUI.goToURI();
|
||||
},
|
||||
|
||||
selectAll: function cc_selectAll() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
if (target.localName == "browser") {
|
||||
let x = ContextMenuUI.popupState.x;
|
||||
let y = ContextMenuUI.popupState.y;
|
||||
let json = {x: x, y: y, command: "select-all" };
|
||||
target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
|
||||
} else {
|
||||
target.editor.selectAll();
|
||||
target.focus();
|
||||
}
|
||||
},
|
||||
|
||||
openInNewTab: function cc_openInNewTab() {
|
||||
Browser.addTab(ContextMenuUI.popupState.linkURL, false, Browser.selectedTab);
|
||||
ContextUI.peekTabs();
|
||||
},
|
||||
|
||||
saveToWinLibrary: function cc_saveToWinLibrary(aType) {
|
||||
let popupState = ContextMenuUI.popupState;
|
||||
let browser = popupState.target;
|
||||
|
||||
// ContentAreaUtils internalSave relies on various desktop related prefs,
|
||||
// values, and functionality. We want to be more direct by saving the
|
||||
// image to the users Windows Library. If users want to specify the
|
||||
// save location they can use the context menu option 'Save (type) To'.
|
||||
|
||||
let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties);
|
||||
let saveLocationPath = dirSvc.get(aType, Components.interfaces.nsIFile);
|
||||
|
||||
let fileInfo = new ContentAreaUtils.FileInfo();
|
||||
ContentAreaUtils.initFileInfo(fileInfo, popupState.mediaURL,
|
||||
browser.documentURI.originCharset,
|
||||
null, null, null);
|
||||
let filename =
|
||||
ContentAreaUtils.getNormalizedLeafName(fileInfo.fileName, fileInfo.fileExt);
|
||||
saveLocationPath.append(filename);
|
||||
|
||||
// Start the background save process
|
||||
ContentAreaUtils.internalPersist({
|
||||
sourceURI : fileInfo.uri,
|
||||
sourceDocument : null,
|
||||
sourceReferrer : browser.documentURI,
|
||||
targetContentType : popupState.contentType,
|
||||
targetFile : saveLocationPath,
|
||||
sourceCacheKey : null,
|
||||
sourcePostData : null,
|
||||
bypassCache : false,
|
||||
initiatingWindow : this.docRef.defaultView
|
||||
});
|
||||
},
|
||||
|
||||
saveVideo: function cc_saveVideo() {
|
||||
this.saveToWinLibrary("Vids");
|
||||
},
|
||||
|
||||
saveVideoTo: function cc_saveVideoTo() {
|
||||
this.saveFileAs(ContextMenuUI.popupState);
|
||||
},
|
||||
|
||||
saveImage: function cc_saveImage() {
|
||||
this.saveToWinLibrary("Pict");
|
||||
},
|
||||
|
||||
saveImageTo: function cc_saveImageTo() {
|
||||
this.saveFileAs(ContextMenuUI.popupState);
|
||||
},
|
||||
|
||||
copyLink: function cc_copyLink() {
|
||||
this.clipboard.copyString(ContextMenuUI.popupState.linkURL,
|
||||
this.docRef);
|
||||
},
|
||||
|
||||
copyEmail: function cc_copyEmail() {
|
||||
this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
|
||||
this.docRef);
|
||||
},
|
||||
|
||||
copyPhone: function cc_copyPhone() {
|
||||
this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
|
||||
this.docRef);
|
||||
},
|
||||
|
||||
copyImageLocation: function cc_copyImageLocation() {
|
||||
this.clipboard.copyString(ContextMenuUI.popupState.mediaURL,
|
||||
this.docRef);
|
||||
},
|
||||
|
||||
bookmarkLink: function cc_bookmarkLink() {
|
||||
let state = ContextMenuUI.popupState;
|
||||
let uri = Util.makeURI(state.linkURL);
|
||||
let title = state.linkTitle || state.linkURL;
|
||||
|
||||
try {
|
||||
Bookmarks.addForURI(uri, title);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = Strings.browser.GetStringFromName("alertLinkBookmarked");
|
||||
let toaster = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
|
||||
toaster.showAlertNotification(null, message, "", false, "", null);
|
||||
},
|
||||
|
||||
sendCommand: function cc_playVideo(aCommand) {
|
||||
let browser = ContextMenuUI.popupState.target;
|
||||
browser.messageManager.sendAsyncMessage("Browser:ContextCommand", { command: aCommand });
|
||||
},
|
||||
|
||||
editBookmark: function cc_editBookmark() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
target.startEditing();
|
||||
},
|
||||
|
||||
removeBookmark: function cc_removeBookmark() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
target.remove();
|
||||
},
|
||||
|
||||
shortcutBookmark: function cc_shortcutBookmark() {
|
||||
let target = ContextMenuUI.popupState.target;
|
||||
Util.createShortcut(target.getAttribute("title"), target.getAttribute("uri"), target.getAttribute("src"), "bookmark");
|
||||
},
|
||||
|
||||
findInPage: function cc_findInPage() {
|
||||
dump('ContextCommand: findInPage');
|
||||
FindHelperUI.show();
|
||||
},
|
||||
|
||||
viewOnDesktop: function cc_viewOnDesktop() {
|
||||
dump('ContextCommand: viewOnDesktop');
|
||||
Appbar.onViewOnDesktop();
|
||||
},
|
||||
|
||||
/*
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
/*
|
||||
* isAccessibleDirectory
|
||||
*
|
||||
* Test to see if the directory exists and is writable.
|
||||
*/
|
||||
isAccessibleDirectory: function isAccessibleDirectory(aDirectory) {
|
||||
return aDirectory && aDirectory.exists() && aDirectory.isDirectory() &&
|
||||
aDirectory.isWritable();
|
||||
},
|
||||
|
||||
/*
|
||||
* saveFileAs
|
||||
*
|
||||
* Browse for a location and then save a file to the local system using
|
||||
* standard download prefs.
|
||||
*
|
||||
* @param aFileUriString path to file
|
||||
*/
|
||||
saveFileAs: function saveFileAs(aPopupState) {
|
||||
let srcUri = null;
|
||||
let mediaURL = aPopupState.mediaURL;
|
||||
try {
|
||||
srcUri = Util.makeURI(mediaURL, null, null);
|
||||
} catch (ex) {
|
||||
Util.dumpLn("could not parse:", mediaURL);
|
||||
return;
|
||||
}
|
||||
|
||||
let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
let windowTitle = Strings.browser.GetStringFromName("browserForSaveLocation");
|
||||
|
||||
picker.init(window, windowTitle, Ci.nsIFilePicker.modeSave);
|
||||
|
||||
// prefered filename
|
||||
let fileName = mediaURL.substring(mediaURL.lastIndexOf("/") + 1);
|
||||
if (fileName.length)
|
||||
picker.defaultString = fileName;
|
||||
|
||||
// prefered file extension
|
||||
let fileExtension = mediaURL.substring(mediaURL.lastIndexOf(".") + 1);
|
||||
if (fileExtension.length)
|
||||
picker.defaultExtension = fileExtension;
|
||||
picker.appendFilters(Ci.nsIFilePicker.filterImages);
|
||||
|
||||
// prefered save location
|
||||
var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
picker.displayDirectory = dnldMgr.userDownloadsDirectory;
|
||||
try {
|
||||
let lastDir = Services.prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
|
||||
if (this.isAccessibleDirectory(lastDir))
|
||||
picker.displayDirectory = lastDir;
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
this._picker = picker;
|
||||
this._pickerUrl = mediaURL;
|
||||
this._pickerContentDisp = aPopupState.contentDisposition;
|
||||
this._contentType = aPopupState.contentType;
|
||||
picker.open(ContextCommands);
|
||||
},
|
||||
|
||||
/*
|
||||
* Filepicker callback
|
||||
*/
|
||||
done: function done(aSuccess) {
|
||||
let picker = this._picker;
|
||||
this._picker = null;
|
||||
|
||||
if (aSuccess == Ci.nsIFilePicker.returnCancel)
|
||||
return;
|
||||
|
||||
let file = picker.file;
|
||||
if (file == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
if (file.exists())
|
||||
file.remove(false);
|
||||
} catch (e) {}
|
||||
|
||||
ContentAreaUtils.internalSave(this._pickerUrl, null, null,
|
||||
this._pickerContentDisp,
|
||||
this._contentType, false, "SaveImageTitle",
|
||||
new AutoChosen(file, Util.makeURI(this._pickerUrl, null, null)),
|
||||
getBrowser().documentURI, this.docRef, true, null);
|
||||
|
||||
var newDir = file.parent.QueryInterface(Ci.nsILocalFile);
|
||||
Services.prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
|
||||
this.file = aFileAutoChosen;
|
||||
this.uri = aUriAutoChosen;
|
||||
}
|
||||
|
|
@ -0,0 +1,818 @@
|
|||
/* 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/. */
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
var loginManager = {
|
||||
|
||||
/* ---------- private members ---------- */
|
||||
|
||||
|
||||
get _formFillService() {
|
||||
return this._formFillService =
|
||||
Cc["@mozilla.org/satchel/form-fill-controller;1"].
|
||||
getService(Ci.nsIFormFillController);
|
||||
},
|
||||
|
||||
_nsLoginInfo : null, // Constructor for nsILoginInfo implementation
|
||||
_debug : false, // mirrors signon.debug
|
||||
_remember : true, // mirrors signon.rememberSignons preference
|
||||
|
||||
init : function () {
|
||||
// Cache references to current |this| in utility objects
|
||||
this._webProgressListener._domEventListener = this._domEventListener;
|
||||
this._webProgressListener._pwmgr = this;
|
||||
this._domEventListener._pwmgr = this;
|
||||
this._observer._pwmgr = this;
|
||||
|
||||
// Form submit observer checks forms for new logins and pw changes.
|
||||
Services.obs.addObserver(this._observer, "earlyformsubmit", false);
|
||||
|
||||
// Get constructor for nsILoginInfo
|
||||
this._nsLoginInfo = new Components.Constructor(
|
||||
"@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
|
||||
|
||||
// Preferences. Add observer so we get notified of changes.
|
||||
Services.prefs.addObserver("signon.", this._observer, false);
|
||||
|
||||
// Get current preference values.
|
||||
this._debug = Services.prefs.getBoolPref("signon.debug");
|
||||
this._remember = Services.prefs.getBoolPref("signon.rememberSignons");
|
||||
|
||||
// WebProgressListener for getting notification of new doc loads.
|
||||
var progress = Cc["@mozilla.org/docloaderservice;1"].
|
||||
getService(Ci.nsIWebProgress);
|
||||
progress.addProgressListener(this._webProgressListener,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* log
|
||||
*
|
||||
* Internal function for logging debug messages to the Error Console window
|
||||
*/
|
||||
log : function (message) {
|
||||
if (!this._debug)
|
||||
return;
|
||||
dump("PasswordUtils: " + message + "\n");
|
||||
Services.console.logStringMessage("PasswordUtils: " + message);
|
||||
},
|
||||
|
||||
|
||||
/* fillForm
|
||||
*
|
||||
* Fill the form with login information if we can find it.
|
||||
*/
|
||||
fillForm: function (form) {
|
||||
this._fillForm(form, true, true, false, null);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _fillform
|
||||
*
|
||||
* Fill the form with login information if we can find it.
|
||||
* autofillForm denotes if we should fill the form in automatically
|
||||
* ignoreAutocomplete denotes if we should ignore autocomplete=off attributes
|
||||
* foundLogins is an array of nsILoginInfo
|
||||
*/
|
||||
_fillForm : function (form, autofillForm, ignoreAutocomplete,
|
||||
clobberPassword, foundLogins) {
|
||||
// Heuristically determine what the user/pass fields are
|
||||
// We do this before checking to see if logins are stored,
|
||||
// so that the user isn't prompted for a master password
|
||||
// without need.
|
||||
var [usernameField, passwordField, ignored] =
|
||||
this._getFormFields(form, false);
|
||||
|
||||
// Need a valid password field to do anything.
|
||||
if (passwordField == null)
|
||||
return false;
|
||||
|
||||
// If the fields are disabled or read-only, there's nothing to do.
|
||||
if (passwordField.disabled || passwordField.readOnly ||
|
||||
usernameField && (usernameField.disabled ||
|
||||
usernameField.readOnly)) {
|
||||
this.log("not filling form, login fields disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Discard logins which have username/password values that don't
|
||||
// fit into the fields (as specified by the maxlength attribute).
|
||||
// The user couldn't enter these values anyway, and it helps
|
||||
// with sites that have an extra PIN to be entered (bug 391514)
|
||||
var maxUsernameLen = Number.MAX_VALUE;
|
||||
var maxPasswordLen = Number.MAX_VALUE;
|
||||
|
||||
// If attribute wasn't set, default is -1.
|
||||
if (usernameField && usernameField.maxLength >= 0)
|
||||
maxUsernameLen = usernameField.maxLength;
|
||||
if (passwordField.maxLength >= 0)
|
||||
maxPasswordLen = passwordField.maxLength;
|
||||
|
||||
var logins = foundLogins.filter(function (l) {
|
||||
var fit = (l.username.length <= maxUsernameLen &&
|
||||
l.password.length <= maxPasswordLen);
|
||||
if (!fit)
|
||||
this.log("Ignored " + l.username + " login: won't fit");
|
||||
|
||||
return fit;
|
||||
}, this);
|
||||
|
||||
|
||||
// Nothing to do if we have no matching logins available.
|
||||
if (logins.length == 0)
|
||||
return false;
|
||||
|
||||
|
||||
// The reason we didn't end up filling the form, if any. We include
|
||||
// this in the formInfo object we send with the passwordmgr-found-logins
|
||||
// notification. See the _notifyFoundLogins docs for possible values.
|
||||
var didntFillReason = null;
|
||||
|
||||
// Attach autocomplete stuff to the username field, if we have
|
||||
// one. This is normally used to select from multiple accounts,
|
||||
// but even with one account we should refill if the user edits.
|
||||
if (usernameField)
|
||||
this._attachToInput(usernameField);
|
||||
|
||||
// Don't clobber an existing password.
|
||||
if (passwordField.value && !clobberPassword) {
|
||||
didntFillReason = "existingPassword";
|
||||
this._notifyFoundLogins(didntFillReason, usernameField,
|
||||
passwordField, foundLogins, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the form has an autocomplete=off attribute in play, don't
|
||||
// fill in the login automatically. We check this after attaching
|
||||
// the autocomplete stuff to the username field, so the user can
|
||||
// still manually select a login to be filled in.
|
||||
var isFormDisabled = false;
|
||||
if (!ignoreAutocomplete &&
|
||||
(this._isAutocompleteDisabled(form) ||
|
||||
this._isAutocompleteDisabled(usernameField) ||
|
||||
this._isAutocompleteDisabled(passwordField))) {
|
||||
|
||||
isFormDisabled = true;
|
||||
this.log("form not filled, has autocomplete=off");
|
||||
}
|
||||
|
||||
// Variable such that we reduce code duplication and can be sure we
|
||||
// should be firing notifications if and only if we can fill the form.
|
||||
var selectedLogin = null;
|
||||
|
||||
if (usernameField && usernameField.value) {
|
||||
// If username was specified in the form, only fill in the
|
||||
// password if we find a matching login.
|
||||
var username = usernameField.value.toLowerCase();
|
||||
|
||||
let matchingLogins = logins.filter(function(l)
|
||||
l.username.toLowerCase() == username);
|
||||
if (matchingLogins.length) {
|
||||
selectedLogin = matchingLogins[0];
|
||||
} else {
|
||||
didntFillReason = "existingUsername";
|
||||
this.log("Password not filled. None of the stored " +
|
||||
"logins match the username already present.");
|
||||
}
|
||||
} else if (logins.length == 1) {
|
||||
selectedLogin = logins[0];
|
||||
} else {
|
||||
// We have multiple logins. Handle a special case here, for sites
|
||||
// which have a normal user+pass login *and* a password-only login
|
||||
// (eg, a PIN). Prefer the login that matches the type of the form
|
||||
// (user+pass or pass-only) when there's exactly one that matches.
|
||||
let matchingLogins;
|
||||
if (usernameField)
|
||||
matchingLogins = logins.filter(function(l) l.username);
|
||||
else
|
||||
matchingLogins = logins.filter(function(l) !l.username);
|
||||
if (matchingLogins.length == 1) {
|
||||
selectedLogin = matchingLogins[0];
|
||||
} else {
|
||||
didntFillReason = "multipleLogins";
|
||||
this.log("Multiple logins for form, so not filling any.");
|
||||
}
|
||||
}
|
||||
|
||||
var didFillForm = false;
|
||||
if (selectedLogin && autofillForm && !isFormDisabled) {
|
||||
// Fill the form
|
||||
if (usernameField)
|
||||
usernameField.value = selectedLogin.username;
|
||||
passwordField.value = selectedLogin.password;
|
||||
didFillForm = true;
|
||||
} else if (selectedLogin && !autofillForm) {
|
||||
// For when autofillForm is false, but we still have the information
|
||||
// to fill a form, we notify observers.
|
||||
didntFillReason = "noAutofillForms";
|
||||
Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
|
||||
this.log("autofillForms=false but form can be filled; notified observers");
|
||||
} else if (selectedLogin && isFormDisabled) {
|
||||
// For when autocomplete is off, but we still have the information
|
||||
// to fill a form, we notify observers.
|
||||
didntFillReason = "autocompleteOff";
|
||||
Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
|
||||
this.log("autocomplete=off but form can be filled; notified observers");
|
||||
}
|
||||
|
||||
this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
|
||||
foundLogins, selectedLogin);
|
||||
|
||||
return didFillForm;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getPasswordOrigin
|
||||
*
|
||||
* Get the parts of the URL we want for identification.
|
||||
*/
|
||||
_getPasswordOrigin : function (uriString, allowJS) {
|
||||
var realm = "";
|
||||
try {
|
||||
var uri = Services.io.newURI(uriString, null, null);
|
||||
|
||||
if (allowJS && uri.scheme == "javascript")
|
||||
return "javascript:"
|
||||
|
||||
realm = uri.scheme + "://" + uri.host;
|
||||
|
||||
// If the URI explicitly specified a port, only include it when
|
||||
// it's not the default. (We never want "http://foo.com:80")
|
||||
var port = uri.port;
|
||||
if (port != -1) {
|
||||
var handler = Services.io.getProtocolHandler(uri.scheme);
|
||||
if (port != handler.defaultPort)
|
||||
realm += ":" + port;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// bug 159484 - disallow url types that don't support a hostPort.
|
||||
// (although we handle "javascript:..." as a special case above.)
|
||||
this.log("Couldn't parse origin for " + uriString);
|
||||
realm = null;
|
||||
}
|
||||
|
||||
return realm;
|
||||
},
|
||||
|
||||
|
||||
_getActionOrigin : function (form) {
|
||||
var uriString = form.action;
|
||||
|
||||
// A blank or mission action submits to where it came from.
|
||||
if (uriString == "")
|
||||
uriString = form.baseURI; // ala bug 297761
|
||||
|
||||
return this._getPasswordOrigin(uriString, true);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _isAutoCompleteDisabled
|
||||
*
|
||||
* Returns true if the page requests autocomplete be disabled for the
|
||||
* specified form input.
|
||||
*/
|
||||
_isAutocompleteDisabled : function (element) {
|
||||
if (element && element.hasAttribute("autocomplete") &&
|
||||
element.getAttribute("autocomplete").toLowerCase() == "off")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getFormFields
|
||||
*
|
||||
* Returns the username and password fields found in the form.
|
||||
* Can handle complex forms by trying to figure out what the
|
||||
* relevant fields are.
|
||||
*
|
||||
* Returns: [usernameField, newPasswordField, oldPasswordField]
|
||||
*
|
||||
* usernameField may be null.
|
||||
* newPasswordField will always be non-null.
|
||||
* oldPasswordField may be null. If null, newPasswordField is just
|
||||
* "theLoginField". If not null, the form is apparently a
|
||||
* change-password field, with oldPasswordField containing the password
|
||||
* that is being changed.
|
||||
*/
|
||||
_getFormFields : function (form, isSubmission) {
|
||||
var usernameField = null;
|
||||
|
||||
// Locate the password field(s) in the form. Up to 3 supported.
|
||||
// If there's no password field, there's nothing for us to do.
|
||||
var pwFields = this._getPasswordFields(form, isSubmission);
|
||||
if (!pwFields)
|
||||
return [null, null, null];
|
||||
|
||||
|
||||
// Locate the username field in the form by searching backwards
|
||||
// from the first passwordfield, assume the first text field is the
|
||||
// username. We might not find a username field if the user is
|
||||
// already logged in to the site.
|
||||
for (var i = pwFields[0].index - 1; i >= 0; i--) {
|
||||
var element = form.elements[i];
|
||||
var fieldType = (element.hasAttribute("type") ?
|
||||
element.getAttribute("type").toLowerCase() :
|
||||
element.type);
|
||||
if (fieldType == "text" ||
|
||||
fieldType == "email" ||
|
||||
fieldType == "url" ||
|
||||
fieldType == "tel" ||
|
||||
fieldType == "number") {
|
||||
usernameField = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!usernameField)
|
||||
this.log("(form -- no username field found)");
|
||||
|
||||
|
||||
// If we're not submitting a form (it's a page load), there are no
|
||||
// password field values for us to use for identifying fields. So,
|
||||
// just assume the first password field is the one to be filled in.
|
||||
if (!isSubmission || pwFields.length == 1)
|
||||
return [usernameField, pwFields[0].element, null];
|
||||
|
||||
|
||||
// Try to figure out WTF is in the form based on the password values.
|
||||
var oldPasswordField, newPasswordField;
|
||||
var pw1 = pwFields[0].element.value;
|
||||
var pw2 = pwFields[1].element.value;
|
||||
var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
|
||||
|
||||
if (pwFields.length == 3) {
|
||||
// Look for two identical passwords, that's the new password
|
||||
|
||||
if (pw1 == pw2 && pw2 == pw3) {
|
||||
// All 3 passwords the same? Weird! Treat as if 1 pw field.
|
||||
newPasswordField = pwFields[0].element;
|
||||
oldPasswordField = null;
|
||||
} else if (pw1 == pw2) {
|
||||
newPasswordField = pwFields[0].element;
|
||||
oldPasswordField = pwFields[2].element;
|
||||
} else if (pw2 == pw3) {
|
||||
oldPasswordField = pwFields[0].element;
|
||||
newPasswordField = pwFields[2].element;
|
||||
} else if (pw1 == pw3) {
|
||||
// A bit odd, but could make sense with the right page layout.
|
||||
newPasswordField = pwFields[0].element;
|
||||
oldPasswordField = pwFields[1].element;
|
||||
} else {
|
||||
// We can't tell which of the 3 passwords should be saved.
|
||||
this.log("(form ignored -- all 3 pw fields differ)");
|
||||
return [null, null, null];
|
||||
}
|
||||
} else { // pwFields.length == 2
|
||||
if (pw1 == pw2) {
|
||||
// Treat as if 1 pw field
|
||||
newPasswordField = pwFields[0].element;
|
||||
oldPasswordField = null;
|
||||
} else {
|
||||
// Just assume that the 2nd password is the new password
|
||||
oldPasswordField = pwFields[0].element;
|
||||
newPasswordField = pwFields[1].element;
|
||||
}
|
||||
}
|
||||
|
||||
return [usernameField, newPasswordField, oldPasswordField];
|
||||
},
|
||||
|
||||
|
||||
/* ---------- Private methods ---------- */
|
||||
|
||||
|
||||
/*
|
||||
* _getPasswordFields
|
||||
*
|
||||
* Returns an array of password field elements for the specified form.
|
||||
* If no pw fields are found, or if more than 3 are found, then null
|
||||
* is returned.
|
||||
*
|
||||
* skipEmptyFields can be set to ignore password fields with no value.
|
||||
*/
|
||||
_getPasswordFields : function (form, skipEmptyFields) {
|
||||
// Locate the password fields in the form.
|
||||
var pwFields = [];
|
||||
for (var i = 0; i < form.elements.length; i++) {
|
||||
var element = form.elements[i];
|
||||
if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
|
||||
element.type != "password")
|
||||
continue;
|
||||
|
||||
if (skipEmptyFields && !element.value)
|
||||
continue;
|
||||
|
||||
pwFields[pwFields.length] = {
|
||||
index : i,
|
||||
element : element
|
||||
};
|
||||
}
|
||||
|
||||
// If too few or too many fields, bail out.
|
||||
if (pwFields.length == 0) {
|
||||
this.log("(form ignored -- no password fields.)");
|
||||
return null;
|
||||
} else if (pwFields.length > 3) {
|
||||
this.log("(form ignored -- too many password fields. [got " +
|
||||
pwFields.length + "])");
|
||||
return null;
|
||||
}
|
||||
|
||||
return pwFields;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Notify observers about an attempt to fill a form that resulted in some
|
||||
* saved logins being found for the form.
|
||||
*
|
||||
* This does not get called if the login manager attempts to fill a form
|
||||
* but does not find any saved logins. It does, however, get called when
|
||||
* the login manager does find saved logins whether or not it actually
|
||||
* fills the form with one of them.
|
||||
*
|
||||
* @param didntFillReason {String}
|
||||
* the reason the login manager didn't fill the form, if any;
|
||||
* if the value of this parameter is null, then the form was filled;
|
||||
* otherwise, this parameter will be one of these values:
|
||||
* existingUsername: the username field already contains a username
|
||||
* that doesn't match any stored usernames
|
||||
* existingPassword: the password field already contains a password
|
||||
* autocompleteOff: autocomplete has been disabled for the form
|
||||
* or its username or password fields
|
||||
* multipleLogins: we have multiple logins for the form
|
||||
* noAutofillForms: the autofillForms pref is set to false
|
||||
*
|
||||
* @param usernameField {HTMLInputElement}
|
||||
* the username field detected by the login manager, if any;
|
||||
* otherwise null
|
||||
*
|
||||
* @param passwordField {HTMLInputElement}
|
||||
* the password field detected by the login manager
|
||||
*
|
||||
* @param foundLogins {Array}
|
||||
* an array of nsILoginInfos that can be used to fill the form
|
||||
*
|
||||
* @param selectedLogin {nsILoginInfo}
|
||||
* the nsILoginInfo that was/would be used to fill the form, if any;
|
||||
* otherwise null; whether or not it was actually used depends on
|
||||
* the value of the didntFillReason parameter
|
||||
*/
|
||||
_notifyFoundLogins : function (didntFillReason, usernameField,
|
||||
passwordField, foundLogins, selectedLogin) {
|
||||
// We need .setProperty(), which is a method on the original
|
||||
// nsIWritablePropertyBag. Strangley enough, nsIWritablePropertyBag2
|
||||
// doesn't inherit from that, so the additional QI is needed.
|
||||
let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag2).
|
||||
QueryInterface(Ci.nsIWritablePropertyBag);
|
||||
|
||||
formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
|
||||
formInfo.setPropertyAsInterface("usernameField", usernameField);
|
||||
formInfo.setPropertyAsInterface("passwordField", passwordField);
|
||||
formInfo.setProperty("foundLogins", foundLogins.concat());
|
||||
formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
|
||||
|
||||
Services.obs.notifyObservers(formInfo, "passwordmgr-found-logins", null);
|
||||
},
|
||||
|
||||
/*
|
||||
* _attachToInput
|
||||
*
|
||||
* Hooks up autocomplete support to a username field, to allow
|
||||
* a user editing the field to select an existing login and have
|
||||
* the password field filled in.
|
||||
*/
|
||||
_attachToInput : function (element) {
|
||||
this.log("attaching autocomplete stuff");
|
||||
element.addEventListener("blur",
|
||||
this._domEventListener, false);
|
||||
element.addEventListener("DOMAutoComplete",
|
||||
this._domEventListener, false);
|
||||
this._formFillService.markAsLoginManagerField(element);
|
||||
},
|
||||
|
||||
/*
|
||||
* _fillDocument
|
||||
*
|
||||
* Called when a page has loaded. For each form in the document,
|
||||
* we ask the parent process to see if it can be filled with a stored login
|
||||
* and fill them in with the results
|
||||
*/
|
||||
_fillDocument : function (doc) {
|
||||
var forms = doc.forms;
|
||||
if (!forms || forms.length == 0)
|
||||
return;
|
||||
|
||||
this.log("_fillDocument processing " + forms.length +
|
||||
" forms on " + doc.documentURI);
|
||||
|
||||
var autofillForm = !PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) &&
|
||||
Services.prefs.getBoolPref("signon.autofillForms");
|
||||
|
||||
// actionOrigins is a list of each form's action origins for this
|
||||
// document. The parent process needs this to find the passwords
|
||||
// for each action origin.
|
||||
var actionOrigins = [];
|
||||
|
||||
for (var i = 0; i < forms.length; i++) {
|
||||
var form = forms[i];
|
||||
let [, passwordField, ] = this._getFormFields(form, false);
|
||||
if (passwordField) {
|
||||
var actionOrigin = this._getActionOrigin(form);
|
||||
actionOrigins.push(actionOrigin);
|
||||
}
|
||||
} // foreach form
|
||||
|
||||
if (!actionOrigins.length)
|
||||
return;
|
||||
|
||||
var formOrigin = this._getPasswordOrigin(doc.documentURI);
|
||||
var foundLogins = this._getPasswords(actionOrigins, formOrigin);
|
||||
|
||||
for (var i = 0; i < forms.length; i++) {
|
||||
var form = forms[i];
|
||||
var actionOrigin = this._getActionOrigin(form);
|
||||
if (foundLogins[actionOrigin]) {
|
||||
this.log("_fillDocument processing form[" + i + "]");
|
||||
this._fillForm(form, autofillForm, false, false,
|
||||
foundLogins[actionOrigin]);
|
||||
}
|
||||
} // foreach form
|
||||
},
|
||||
|
||||
/*
|
||||
* _getPasswords
|
||||
*
|
||||
* Retrieve passwords from parent process and prepare logins to be passed to
|
||||
* _fillForm. Returns map from action origins to passwords.
|
||||
*/
|
||||
_getPasswords: function(actionOrigins, formOrigin) {
|
||||
// foundLogins will be a map from action origins to passwords.
|
||||
var message = sendSyncMessage("PasswordMgr:GetPasswords", {
|
||||
actionOrigins: actionOrigins,
|
||||
formOrigin: formOrigin
|
||||
})[0];
|
||||
|
||||
// XXX need to somehow respond to the UI being busy
|
||||
// not needed for Fennec yet
|
||||
|
||||
var foundLogins = message.foundLogins;
|
||||
|
||||
// Each password will be a JSON-unserialized object, but they need to be
|
||||
// nsILoginInfo's.
|
||||
for (var key in foundLogins) {
|
||||
var logins = foundLogins[key];
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
var obj = logins[i];
|
||||
logins[i] = new this._nsLoginInfo();
|
||||
logins[i].init(obj.hostname, obj.formSubmitURL, obj.httpRealm,
|
||||
obj.username, obj.password,
|
||||
obj.usernameField, obj.passwordField);
|
||||
}
|
||||
}
|
||||
|
||||
return foundLogins;
|
||||
},
|
||||
|
||||
/*
|
||||
* _onFormSubmit
|
||||
*
|
||||
* Called by the our observer when notified of a form submission.
|
||||
* [Note that this happens before any DOM onsubmit handlers are invoked.]
|
||||
* Looks for a password change in the submitted form, so we can update
|
||||
* our stored password.
|
||||
*/
|
||||
_onFormSubmit : function (form) {
|
||||
var doc = form.ownerDocument;
|
||||
var win = doc.defaultView;
|
||||
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(win)) {
|
||||
// We won't do anything in private browsing mode anyway,
|
||||
// so there's no need to perform further checks.
|
||||
this.log("(form submission ignored in private browsing mode)");
|
||||
return;
|
||||
}
|
||||
|
||||
// If password saving is disabled (globally or for host), bail out now.
|
||||
if (!this._remember)
|
||||
return;
|
||||
|
||||
var hostname = this._getPasswordOrigin(doc.documentURI);
|
||||
var formSubmitURL = this._getActionOrigin(form);
|
||||
|
||||
|
||||
// Get the appropriate fields from the form.
|
||||
var [usernameField, newPasswordField, oldPasswordField] =
|
||||
this._getFormFields(form, true);
|
||||
|
||||
// Need at least 1 valid password field to do anything.
|
||||
if (newPasswordField == null)
|
||||
return;
|
||||
|
||||
// Check for autocomplete=off attribute. We don't use it to prevent
|
||||
// autofilling (for existing logins), but won't save logins when it's
|
||||
// present.
|
||||
// XXX spin out a bug that we don't update timeLastUsed in this case?
|
||||
if (this._isAutocompleteDisabled(form) ||
|
||||
this._isAutocompleteDisabled(usernameField) ||
|
||||
this._isAutocompleteDisabled(newPasswordField) ||
|
||||
this._isAutocompleteDisabled(oldPasswordField)) {
|
||||
this.log("(form submission ignored -- autocomplete=off found)");
|
||||
return;
|
||||
}
|
||||
|
||||
sendSyncMessage("PasswordMgr:FormSubmitted", {
|
||||
hostname: hostname,
|
||||
formSubmitURL: formSubmitURL,
|
||||
usernameField: usernameField ? usernameField.name : "",
|
||||
usernameValue: usernameField ? usernameField.value : "",
|
||||
passwordField: newPasswordField.name,
|
||||
passwordValue: newPasswordField.value,
|
||||
hasOldPasswordField: !!oldPasswordField
|
||||
});
|
||||
},
|
||||
|
||||
/* ---------- Utility objects ---------- */
|
||||
|
||||
/*
|
||||
* _observer object
|
||||
*
|
||||
* Internal utility object, implements the nsIObserver interface.
|
||||
* Used to receive notification for: form submission, preference changes.
|
||||
*/
|
||||
_observer : {
|
||||
_pwmgr : null,
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsIFormSubmitObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
|
||||
// nsFormSubmitObserver
|
||||
notify : function (formElement, aWindow, actionURI) {
|
||||
// Counterintuitively, form submit observers fire for content that
|
||||
// may not be the content in this context.
|
||||
if (aWindow.top != content)
|
||||
return true;
|
||||
|
||||
this._pwmgr.log("observer notified for form submission.");
|
||||
|
||||
// We're invoked before the content's |onsubmit| handlers, so we
|
||||
// can grab form data before it might be modified (see bug 257781).
|
||||
|
||||
try {
|
||||
this._pwmgr._onFormSubmit(formElement);
|
||||
} catch (e) {
|
||||
this._pwmgr.log("Caught error in onFormSubmit: " + e);
|
||||
}
|
||||
|
||||
return true; // Always return true, or form submit will be canceled.
|
||||
},
|
||||
|
||||
observe : function (aSubject, aTopic, aData) {
|
||||
this._pwmgr._debug = Services.prefs.getBoolPref("signon.debug");
|
||||
this._pwmgr._remember = Services.prefs.getBoolPref("signon.rememberSignons");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _webProgressListener object
|
||||
*
|
||||
* Internal utility object, implements nsIWebProgressListener interface.
|
||||
* This is attached to the document loader service, so we get
|
||||
* notifications about all page loads.
|
||||
*/
|
||||
_webProgressListener : {
|
||||
_pwmgr : null,
|
||||
_domEventListener : null,
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
|
||||
onStateChange : function (aWebProgress, aRequest,
|
||||
aStateFlags, aStatus) {
|
||||
|
||||
// STATE_START is too early, doc is still the old page.
|
||||
if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_TRANSFERRING))
|
||||
return;
|
||||
|
||||
if (!this._pwmgr._remember)
|
||||
return;
|
||||
|
||||
var domWin = aWebProgress.DOMWindow;
|
||||
var domDoc = domWin.document;
|
||||
|
||||
// Only process things which might have HTML forms.
|
||||
if (!(domDoc instanceof Ci.nsIDOMHTMLDocument))
|
||||
return;
|
||||
if (this._pwmgr._debug) {
|
||||
let requestName = "(null)";
|
||||
if (aRequest) {
|
||||
try {
|
||||
requestName = aRequest.name;
|
||||
} catch (ex if ex.result == Components.results.NS_ERROR_NOT_IMPLEMENTED) {
|
||||
// do nothing - leave requestName = "(null)"
|
||||
}
|
||||
}
|
||||
this._pwmgr.log("onStateChange accepted: req = " + requestName +
|
||||
", flags = 0x" + aStateFlags.toString(16));
|
||||
}
|
||||
|
||||
// Fastback doesn't fire DOMContentLoaded, so process forms now.
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) {
|
||||
this._pwmgr.log("onStateChange: restoring document");
|
||||
return this._pwmgr._fillDocument(domDoc);
|
||||
}
|
||||
|
||||
// Add event listener to process page when DOM is complete.
|
||||
domDoc.addEventListener("DOMContentLoaded",
|
||||
this._domEventListener, false);
|
||||
return;
|
||||
},
|
||||
|
||||
// stubs for the nsIWebProgressListener interfaces which we don't use.
|
||||
onProgressChange : function() { throw "Unexpected onProgressChange"; },
|
||||
onLocationChange : function() { throw "Unexpected onLocationChange"; },
|
||||
onStatusChange : function() { throw "Unexpected onStatusChange"; },
|
||||
onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
|
||||
},
|
||||
|
||||
/*
|
||||
* _domEventListener object
|
||||
*
|
||||
* Internal utility object, implements nsIDOMEventListener
|
||||
* Used to catch certain DOM events needed to properly implement form fill.
|
||||
*/
|
||||
_domEventListener : {
|
||||
_pwmgr : null,
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
|
||||
handleEvent : function (event) {
|
||||
if (!event.isTrusted)
|
||||
return;
|
||||
|
||||
this._pwmgr.log("domEventListener: got event " + event.type);
|
||||
|
||||
switch (event.type) {
|
||||
case "DOMAutoComplete":
|
||||
case "blur":
|
||||
var acInputField = event.target;
|
||||
var acForm = acInputField.form;
|
||||
|
||||
// If the username is blank, bail out now -- we don't want
|
||||
// _fillForm() to try filling in a login without a username
|
||||
// to filter on (bug 471906).
|
||||
if (!acInputField.value)
|
||||
return;
|
||||
|
||||
// Make sure the username field _fillForm will use is the
|
||||
// same field as the autocomplete was activated on. If
|
||||
// not, the DOM has been altered and we'll just give up.
|
||||
var [usernameField, passwordField, ignored] =
|
||||
this._pwmgr._getFormFields(acForm, false);
|
||||
if (usernameField == acInputField && passwordField) {
|
||||
var actionOrigin = this._pwmgr._getActionOrigin(acForm);
|
||||
var formOrigin = this._pwmgr._getPasswordOrigin(acForm.ownerDocument.documentURI);
|
||||
var foundLogins = this._pwmgr._getPasswords([actionOrigin], formOrigin);
|
||||
this._pwmgr._fillForm(acForm, true, true, true, foundLogins[actionOrigin]);
|
||||
} else {
|
||||
this._pwmgr.log("Oops, form changed before AC invoked");
|
||||
}
|
||||
return;
|
||||
|
||||
case "DOMContentLoaded":
|
||||
// Only process when we need to
|
||||
event.currentTarget.removeEventListener(event.type, this);
|
||||
if (this._pwmgr._remember && event.target instanceof Ci.nsIDOMHTMLDocument)
|
||||
this._pwmgr._fillDocument(event.target);
|
||||
break;
|
||||
|
||||
default:
|
||||
this._pwmgr.log("Oops! This event unexpected.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loginManager.init();
|
|
@ -0,0 +1,180 @@
|
|||
/* 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/. */
|
||||
|
||||
var PageActions = {
|
||||
_handlers: null,
|
||||
|
||||
init: function init() {
|
||||
if (this._handlers)
|
||||
return;
|
||||
|
||||
this._handlers = [];
|
||||
document.getElementById("pageactions-container").addEventListener("click", this, true);
|
||||
|
||||
this.register("pageaction-reset", this.updatePagePermissions, this);
|
||||
this.register("pageaction-password", this.updateForgetPassword, this);
|
||||
#ifdef NS_PRINTING
|
||||
this.register("pageaction-saveas", this.updatePageSaveAs, this);
|
||||
#endif
|
||||
this.register("pageaction-share", this.updateShare, this);
|
||||
|
||||
CharsetMenu.init();
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
IdentityUI.hide();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param aId id of a pageaction element
|
||||
* @param aCallback function that takes an element and returns true if it should be visible
|
||||
* @param aThisObj (optional) scope object for aCallback
|
||||
*/
|
||||
register: function register(aId, aCallback, aThisObj) {
|
||||
this.init();
|
||||
this._handlers.push({id: aId, callback: aCallback, obj: aThisObj});
|
||||
},
|
||||
|
||||
updateSiteMenu: function updateSiteMenu() {
|
||||
this.init();
|
||||
this._handlers.forEach(function(action) {
|
||||
let node = document.getElementById(action.id);
|
||||
if (node)
|
||||
node.hidden = !action.callback.call(action.obj, node);
|
||||
});
|
||||
this._updateAttributes();
|
||||
},
|
||||
|
||||
get _loginManager() {
|
||||
delete this._loginManager;
|
||||
return this._loginManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
||||
},
|
||||
|
||||
// Permissions we track in Page Actions
|
||||
_permissions: ["popup", "offline-app", "geolocation", "desktop-notification", "openWebappsManage"],
|
||||
|
||||
_forEachPermissions: function _forEachPermissions(aHost, aCallback) {
|
||||
let pm = Services.perms;
|
||||
for (let i = 0; i < this._permissions.length; i++) {
|
||||
let type = this._permissions[i];
|
||||
if (!pm.testPermission(aHost, type))
|
||||
continue;
|
||||
|
||||
let perms = pm.enumerator;
|
||||
while (perms.hasMoreElements()) {
|
||||
let permission = perms.getNext().QueryInterface(Ci.nsIPermission);
|
||||
if (permission.host == aHost.asciiHost && permission.type == type)
|
||||
aCallback(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updatePagePermissions: function updatePagePermissions(aNode) {
|
||||
let host = Browser.selectedBrowser.currentURI;
|
||||
let permissions = [];
|
||||
|
||||
this._forEachPermissions(host, function(aType) {
|
||||
permissions.push("pageactions." + aType);
|
||||
});
|
||||
|
||||
if (!this._loginManager.getLoginSavingEnabled(host.prePath)) {
|
||||
// If rememberSignons is false, then getLoginSavingEnabled returns false
|
||||
// for all pages, so we should just ignore it (Bug 601163).
|
||||
if (Services.prefs.getBoolPref("signon.rememberSignons"))
|
||||
permissions.push("pageactions.password");
|
||||
}
|
||||
|
||||
let descriptions = permissions.map(function(s) Strings.browser.GetStringFromName(s));
|
||||
aNode.setAttribute("description", descriptions.join(", "));
|
||||
|
||||
return (permissions.length > 0);
|
||||
},
|
||||
|
||||
updateForgetPassword: function updateForgetPassword(aNode) {
|
||||
let host = Browser.selectedBrowser.currentURI;
|
||||
let logins = this._loginManager.findLogins({}, host.prePath, "", "");
|
||||
|
||||
return logins.some(function(login) login.hostname == host.prePath);
|
||||
},
|
||||
|
||||
forgetPassword: function forgetPassword(aEvent) {
|
||||
let host = Browser.selectedBrowser.currentURI;
|
||||
let lm = this._loginManager;
|
||||
|
||||
lm.findLogins({}, host.prePath, "", "").forEach(function(login) {
|
||||
if (login.hostname == host.prePath)
|
||||
lm.removeLogin(login);
|
||||
});
|
||||
|
||||
this.hideItem(aEvent.target);
|
||||
aEvent.stopPropagation(); // Don't hide the site menu.
|
||||
},
|
||||
|
||||
clearPagePermissions: function clearPagePermissions(aEvent) {
|
||||
let pm = Services.perms;
|
||||
let host = Browser.selectedBrowser.currentURI;
|
||||
this._forEachPermissions(host, function(aType) {
|
||||
pm.remove(host.asciiHost, aType);
|
||||
|
||||
// reset the 'remember' counter for permissions that support it
|
||||
if (["geolocation", "desktop-notification"].indexOf(aType) != -1)
|
||||
Services.contentPrefs.setPref(host.asciiHost, aType + ".request.remember", 0);
|
||||
});
|
||||
|
||||
let lm = this._loginManager;
|
||||
if (!lm.getLoginSavingEnabled(host.prePath))
|
||||
lm.setLoginSavingEnabled(host.prePath, true);
|
||||
|
||||
this.hideItem(aEvent.target);
|
||||
aEvent.stopPropagation(); // Don't hide the site menu.
|
||||
},
|
||||
|
||||
pinSite : function pinCurrentSite() {
|
||||
if (Browser.selectedBrowser.currentURI.spec.length == 0) {
|
||||
return;
|
||||
}
|
||||
Browser.pinSite();
|
||||
},
|
||||
|
||||
unpinSite : function unpinCurrentSite() {
|
||||
if (Browser.selectedBrowser.currentURI.spec.length == 0) {
|
||||
return;
|
||||
}
|
||||
Browser.unpinSite();
|
||||
},
|
||||
|
||||
updatePageSaveAs: function updatePageSaveAs(aNode) {
|
||||
// Check for local XUL content
|
||||
let contentWindow = Browser.selectedBrowser.contentWindow;
|
||||
return !(contentWindow && contentWindow.document instanceof XULDocument);
|
||||
},
|
||||
|
||||
updateShare: function updateShare(aNode) {
|
||||
return Util.isShareableScheme(Browser.selectedBrowser.currentURI.scheme);
|
||||
},
|
||||
|
||||
hideItem: function hideItem(aNode) {
|
||||
aNode.hidden = true;
|
||||
this._updateAttributes();
|
||||
},
|
||||
|
||||
_updateAttributes: function _updateAttributes() {
|
||||
let container = document.getElementById("pageactions-container");
|
||||
let visibleNodes = container.querySelectorAll("pageaction:not([hidden=true])");
|
||||
let visibleCount = visibleNodes.length;
|
||||
if (visibleCount == 0)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < visibleCount; i++)
|
||||
visibleNodes[i].classList.remove("odd-last-child");
|
||||
|
||||
visibleNodes[visibleCount - 1].classList.add("last-child");
|
||||
if (visibleCount % 2)
|
||||
visibleNodes[visibleCount - 1].classList.add("odd");
|
||||
}
|
||||
};
|
|
@ -0,0 +1,144 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
Components.utils.import("resource://services-sync/main.js");
|
||||
|
||||
/**
|
||||
* Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
|
||||
* fills it with the user's synced tabs.
|
||||
*
|
||||
* @param aSet Control implementing nsIDOMXULSelectControlElement.
|
||||
* @param aSetUIAccess The UI element that should be hidden when Sync is
|
||||
* disabled. Must sanely support 'hidden' attribute.
|
||||
* You may only have one UI access point at this time.
|
||||
*/
|
||||
function RemoteTabsView(aSet, aSetUIAccess) {
|
||||
this._set = aSet;
|
||||
this._set.controller = this;
|
||||
this._uiAccessElement = aSetUIAccess;
|
||||
|
||||
// Sync uses special voodoo observers.
|
||||
// If you want to change this code, talk to the fx-si team
|
||||
Weave.Svc.Obs.add("weave:service:setup-complete", this);
|
||||
Weave.Svc.Obs.add("weave:service:sync:finish", this);
|
||||
Weave.Svc.Obs.add("weave:service:start-over", this);
|
||||
if (this.isSyncEnabled() ) {
|
||||
this.populateTabs();
|
||||
this.populateGrid();
|
||||
this.setUIAccessVisible(true);
|
||||
}
|
||||
else {
|
||||
this.setUIAccessVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
RemoteTabsView.prototype = {
|
||||
_set: null,
|
||||
_uiAccessElement: null,
|
||||
|
||||
handleItemClick: function tabview_handleItemClick(aItem) {
|
||||
let url = aItem.getAttribute("value");
|
||||
BrowserUI.goToURI(url);
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "weave:service:setup-complete":
|
||||
this.populateTabs();
|
||||
this.setUIAccessVisible(true);
|
||||
break;
|
||||
case "weave:service:sync:finish":
|
||||
this.populateGrid();
|
||||
break;
|
||||
case "weave:service:start-over":
|
||||
this.setUIAccessVisible(false);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
setUIAccessVisible: function setUIAccessVisible(aVisible) {
|
||||
this._uiAccessElement.hidden = !aVisible;
|
||||
},
|
||||
|
||||
populateGrid: function populateGrid() {
|
||||
|
||||
let tabsEngine = Weave.Service.engineManager.get("tabs");
|
||||
let list = this._set;
|
||||
let seenURLs = new Set();
|
||||
|
||||
for (let [guid, client] in Iterator(tabsEngine.getAllClients())) {
|
||||
client.tabs.forEach(function({title, urlHistory, icon}) {
|
||||
let url = urlHistory[0];
|
||||
if (tabsEngine.locallyOpenTabMatchesURL(url) || seenURLs.has(url)) {
|
||||
return;
|
||||
}
|
||||
seenURLs.add(url);
|
||||
|
||||
// If we wish to group tabs by client, we should be looking for records
|
||||
// of {type:client, clientName, class:{mobile, desktop}} and will
|
||||
// need to readd logic to reset seenURLs for each client.
|
||||
|
||||
let item = this._set.appendItem((title || url), url);
|
||||
item.setAttribute("iconURI", Weave.Utils.getIcon(icon));
|
||||
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
populateTabs: function populateTabs() {
|
||||
Weave.Service.scheduler.scheduleNextSync(0);
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
Weave.Svc.Obs.remove("weave:service:setup-complete", this);
|
||||
Weave.Svc.Obs.remove("weave:engine:sync:finish", this);
|
||||
Weave.Svc.Obs.remove("weave:service:logout:start-over", this);
|
||||
},
|
||||
|
||||
isSyncEnabled: function isSyncEnabled() {
|
||||
return (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let RemoteTabsStartView = {
|
||||
_view: null,
|
||||
get _grid() { return document.getElementById("start-remotetabs-grid"); },
|
||||
|
||||
init: function init() {
|
||||
let vbox = document.getElementById("start-remotetabs");
|
||||
this._view = new RemoteTabsView(this._grid, vbox);
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
}
|
||||
};
|
||||
|
||||
let RemoteTabsPanelView = {
|
||||
_view: null,
|
||||
|
||||
get _grid() { return document.getElementById("remotetabs-list"); },
|
||||
get visible() { return PanelUI.isPaneVisible("remotetabs-container"); },
|
||||
|
||||
init: function init() {
|
||||
//decks are fragile, don't hide the tab panel(bad things happen), hide link.
|
||||
let menuEntry = document.getElementById("menuitem-remotetabs");
|
||||
this._view = new RemoteTabsView(this._grid, menuEntry);
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,201 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
let prefs = Components.classes["@mozilla.org/preferences-service;1"].
|
||||
getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
// singleton to provide data-level functionality to the views
|
||||
let TopSites = {
|
||||
pinSite: function(aId, aSlotIndex) {
|
||||
Util.dumpLn("TopSites.pinSite: " + aId + ", (TODO)");
|
||||
// FIXME: implementation needed
|
||||
return true; // operation was successful
|
||||
},
|
||||
unpinSite: function(aId) {
|
||||
Util.dumpLn("TopSites.unpinSite: " + aId + ", (TODO)");
|
||||
// FIXME: implementation needed
|
||||
return true; // operation was successful
|
||||
},
|
||||
hideSite: function(aId) {
|
||||
Util.dumpLn("TopSites.hideSite: " + aId + ", (TODO)");
|
||||
// FIXME: implementation needed
|
||||
return true; // operation was successful
|
||||
},
|
||||
restoreSite: function(aId) {
|
||||
Util.dumpLn("TopSites.restoreSite: " + aId + ", (TODO)");
|
||||
// FIXME: implementation needed
|
||||
return true; // operation was successful
|
||||
}
|
||||
};
|
||||
|
||||
function TopSitesView(aGrid, maxSites) {
|
||||
this._set = aGrid;
|
||||
this._set.controller = this;
|
||||
this._topSitesMax = maxSites;
|
||||
|
||||
// handle selectionchange DOM events from the grid/tile group
|
||||
this._set.addEventListener("context-action", this, false);
|
||||
}
|
||||
|
||||
TopSitesView.prototype = {
|
||||
_set:null,
|
||||
_topSitesMax: null,
|
||||
|
||||
handleItemClick: function tabview_handleItemClick(aItem) {
|
||||
let url = aItem.getAttribute("value");
|
||||
BrowserUI.goToURI(url);
|
||||
},
|
||||
|
||||
doActionOnSelectedTiles: function(aActionName) {
|
||||
let tileGroup = this._set;
|
||||
let selectedTiles = tileGroup.selectedItems;
|
||||
|
||||
switch (aActionName){
|
||||
case "delete":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
let id = aNode.getAttribute("data-itemid");
|
||||
// add some class to transition element before deletion?
|
||||
if (TopSites.hideSite(id)) {
|
||||
// success
|
||||
aNode.contextActions.delete('delete');
|
||||
aNode.contextActions.add('restore');
|
||||
}
|
||||
// TODO: we'll use some callback/event to remove the item or re-draw the grid
|
||||
});
|
||||
break;
|
||||
case "pin":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
let id = aNode.getAttribute("data-itemid");
|
||||
if (TopSites.pinSite(id)) {
|
||||
// success
|
||||
aNode.contextActions.delete('pin');
|
||||
aNode.contextActions.add('unpin');
|
||||
}
|
||||
// TODO: we'll use some callback/event to add some class to
|
||||
// indicate element is pinned?
|
||||
});
|
||||
break;
|
||||
case "unpin":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
let id = aNode.getAttribute("data-itemid");
|
||||
if (TopSites.unpinSite(id)) {
|
||||
// success
|
||||
aNode.contextActions.delete('unpin');
|
||||
aNode.contextActions.add('pin');
|
||||
}
|
||||
// TODO: we'll use some callback/event to add some class to
|
||||
// indicate element is pinned (or just redraw grid)
|
||||
});
|
||||
break;
|
||||
// default: no action
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type){
|
||||
case "context-action":
|
||||
this.doActionOnSelectedTiles(aEvent.action);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
populateGrid: function populateGrid() {
|
||||
let query = gHistSvc.getNewQuery();
|
||||
let options = gHistSvc.getNewQueryOptions();
|
||||
options.excludeQueries = true;
|
||||
options.queryType = options.QUERY_TYPE_HISTORY;
|
||||
options.maxResults = this._topSitesMax;
|
||||
options.resultType = options.RESULTS_AS_URI;
|
||||
options.sortingMode = options.SORT_BY_FRECENCY_DESCENDING;
|
||||
|
||||
let result = gHistSvc.executeQuery(query, options);
|
||||
let rootNode = result.root;
|
||||
rootNode.containerOpen = true;
|
||||
let childCount = rootNode.childCount;
|
||||
|
||||
// use this property as the data-itemid attribute on the tiles
|
||||
// which identifies the site
|
||||
let identifier = 'uri';
|
||||
|
||||
function isPinned(aNode) {
|
||||
// placeholder condition,
|
||||
// FIXME: do the actual lookup/check
|
||||
return (aNode.uri.indexOf('google') > -1);
|
||||
}
|
||||
|
||||
for (let i = 0; i < childCount; i++) {
|
||||
let node = rootNode.getChild(i);
|
||||
let uri = node.uri;
|
||||
let title = node.title || uri;
|
||||
|
||||
let supportedActions = ['delete'];
|
||||
// placeholder condition - check field/property for this site
|
||||
if (isPinned(node)) {
|
||||
supportedActions.push('unpin');
|
||||
} else {
|
||||
supportedActions.push('pin');
|
||||
}
|
||||
let item = this._set.appendItem(title, uri);
|
||||
item.setAttribute("iconURI", node.icon);
|
||||
item.setAttribute("data-itemid", node[identifier]);
|
||||
// here is where we could add verbs based on pinned etc. state
|
||||
item.setAttribute("data-contextactions", supportedActions.join(','));
|
||||
}
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
|
||||
isFirstRun: function isFirstRun() {
|
||||
return prefs.getBoolPref("browser.firstrun.show.localepicker");
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
// remove the observers here
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let TopSitesStartView = {
|
||||
_view: null,
|
||||
get _grid() { return document.getElementById("start-topsites-grid"); },
|
||||
|
||||
init: function init() {
|
||||
this._view = new TopSitesView(this._grid, 9);
|
||||
if (this._view.isFirstRun()) {
|
||||
let topsitesVbox = document.getElementById("start-topsites");
|
||||
topsitesVbox.setAttribute("hidden", "true");
|
||||
}
|
||||
this._view.populateGrid();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems(3, 3);
|
||||
},
|
||||
};
|
||||
|
||||
let TopSitesSnappedView = {
|
||||
get _grid() { return document.getElementById("snapped-topsite-grid"); },
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems(1, 9);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
this._view = new TopSitesView(this._grid, 9);
|
||||
if (this._view.isFirstRun()) {
|
||||
let topsitesVbox = document.getElementById("snapped-topsites");
|
||||
topsitesVbox.setAttribute("hidden", "true");
|
||||
}
|
||||
this._view.populateGrid();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
},
|
||||
};
|
|
@ -0,0 +1,412 @@
|
|||
/* 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/. */
|
||||
|
||||
let Util = {
|
||||
/*
|
||||
* General purpose utilities
|
||||
*/
|
||||
|
||||
getWindowUtils: function getWindowUtils(aWindow) {
|
||||
return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
},
|
||||
|
||||
// Recursively find all documents, including root document.
|
||||
getAllDocuments: function getAllDocuments(doc, resultSoFar) {
|
||||
resultSoFar = resultSoFar || [doc];
|
||||
if (!doc.defaultView)
|
||||
return resultSoFar;
|
||||
let frames = doc.defaultView.frames;
|
||||
if (!frames)
|
||||
return resultSoFar;
|
||||
|
||||
let i;
|
||||
let currentDoc;
|
||||
for (i = 0; i < frames.length; i++) {
|
||||
currentDoc = frames[i].document;
|
||||
resultSoFar.push(currentDoc);
|
||||
this.getAllDocuments(currentDoc, resultSoFar);
|
||||
}
|
||||
|
||||
return resultSoFar;
|
||||
},
|
||||
|
||||
// Put the Mozilla networking code into a state that will kick the
|
||||
// auto-connection process.
|
||||
forceOnline: function forceOnline() {
|
||||
Services.io.offline = false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Timing utilties
|
||||
*/
|
||||
|
||||
// Executes aFunc after other events have been processed.
|
||||
executeSoon: function executeSoon(aFunc) {
|
||||
Services.tm.mainThread.dispatch({
|
||||
run: function() {
|
||||
aFunc();
|
||||
}
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
/*
|
||||
* Console printing utilities
|
||||
*/
|
||||
|
||||
dumpf: function dumpf(str) {
|
||||
let args = arguments;
|
||||
let i = 1;
|
||||
dump(str.replace(/%s/g, function() {
|
||||
if (i >= args.length) {
|
||||
throw "dumps received too many placeholders and not enough arguments";
|
||||
}
|
||||
return args[i++].toString();
|
||||
}));
|
||||
},
|
||||
|
||||
// Like dump, but each arg is handled and there's an automatic newline
|
||||
dumpLn: function dumpLn() {
|
||||
for (let i = 0; i < arguments.length; i++)
|
||||
dump(arguments[i] + " ");
|
||||
dump("\n");
|
||||
},
|
||||
|
||||
dumpElement: function dumpElement(aElement) {
|
||||
this.dumpLn(aElement.id);
|
||||
},
|
||||
|
||||
dumpElementTree: function dumpElementTree(aElement) {
|
||||
let node = aElement;
|
||||
while (node) {
|
||||
this.dumpLn("node:", node, "id:", node.id, "class:", node.classList);
|
||||
node = node.parentNode;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Element utilities
|
||||
*/
|
||||
|
||||
highlightElement: function highlightElement(aElement) {
|
||||
if (aElement == null) {
|
||||
this.dumpLn("aElement is null");
|
||||
return;
|
||||
}
|
||||
aElement.style.border = "2px solid red";
|
||||
},
|
||||
|
||||
getHrefForElement: function getHrefForElement(target) {
|
||||
let link = null;
|
||||
while (target) {
|
||||
if (target instanceof Ci.nsIDOMHTMLAnchorElement ||
|
||||
target instanceof Ci.nsIDOMHTMLAreaElement ||
|
||||
target instanceof Ci.nsIDOMHTMLLinkElement) {
|
||||
if (target.hasAttribute("href"))
|
||||
link = target;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
if (link && link.hasAttribute("href"))
|
||||
return link.href;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Rect and nsIDOMRect utilities
|
||||
*/
|
||||
|
||||
pointWithinRect: function pointWithinRect(aX, aY, aRect) {
|
||||
return (aRect.left < aX && aRect.top < aY &&
|
||||
aRect.right > aX && aRect.bottom > aY);
|
||||
},
|
||||
|
||||
pointWithinDOMRect: function pointWithinDOMRect(aX, aY, aRect) {
|
||||
if (!aRect.width || !aRect.height)
|
||||
return false;
|
||||
return this.pointWithinRect(aX, aY, aRect);
|
||||
},
|
||||
|
||||
isEmptyDOMRect: function isEmptyDOMRect(aRect) {
|
||||
if ((aRect.bottom - aRect.top) <= 0 &&
|
||||
(aRect.right - aRect.left) <= 0)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Dumps the details of a dom rect to the console
|
||||
dumpDOMRect: function dumpDOMRect(aMsg, aRect) {
|
||||
try {
|
||||
Util.dumpLn(aMsg,
|
||||
"left:" + Math.round(aRect.left) + ",",
|
||||
"top:" + Math.round(aRect.top) + ",",
|
||||
"right:" + Math.round(aRect.right) + ",",
|
||||
"bottom:" + Math.round(aRect.bottom) + ",",
|
||||
"width:" + Math.round(aRect.right - aRect.left) + ",",
|
||||
"height:" + Math.round(aRect.bottom - aRect.top) );
|
||||
} catch (ex) {
|
||||
Util.dumpLn("dumpDOMRect:", ex.message);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* URIs and schemes
|
||||
*/
|
||||
|
||||
makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
|
||||
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
||||
},
|
||||
|
||||
makeURLAbsolute: function makeURLAbsolute(base, url) {
|
||||
// Note: makeURI() will throw if url is not a valid URI
|
||||
return this.makeURI(url, null, this.makeURI(base)).spec;
|
||||
},
|
||||
|
||||
isLocalScheme: function isLocalScheme(aURL) {
|
||||
return ((aURL.indexOf("about:") == 0 &&
|
||||
aURL != "about:blank" &&
|
||||
aURL != "about:empty" &&
|
||||
aURL != "about:start") ||
|
||||
aURL.indexOf("chrome:") == 0);
|
||||
},
|
||||
|
||||
isOpenableScheme: function isShareableScheme(aProtocol) {
|
||||
let dontOpen = /^(mailto|javascript|news|snews)$/;
|
||||
return (aProtocol && !dontOpen.test(aProtocol));
|
||||
},
|
||||
|
||||
isShareableScheme: function isShareableScheme(aProtocol) {
|
||||
let dontShare = /^(chrome|about|file|javascript|resource)$/;
|
||||
return (aProtocol && !dontShare.test(aProtocol));
|
||||
},
|
||||
|
||||
// Don't display anything in the urlbar for these special URIs.
|
||||
isURLEmpty: function isURLEmpty(aURL) {
|
||||
return (!aURL ||
|
||||
aURL == "about:blank" ||
|
||||
aURL == "about:empty" ||
|
||||
aURL == "about:home" ||
|
||||
aURL == "about:start");
|
||||
},
|
||||
|
||||
// Don't remember these pages in the session store.
|
||||
isURLMemorable: function isURLMemorable(aURL) {
|
||||
return !(aURL == "about:blank" ||
|
||||
aURL == "about:empty" ||
|
||||
aURL == "about:start");
|
||||
},
|
||||
|
||||
/*
|
||||
* Math utilities
|
||||
*/
|
||||
|
||||
clamp: function(num, min, max) {
|
||||
return Math.max(min, Math.min(max, num));
|
||||
},
|
||||
|
||||
/*
|
||||
* Screen and layout utilities
|
||||
*/
|
||||
|
||||
get displayDPI() {
|
||||
delete this.displayDPI;
|
||||
return this.displayDPI = this.getWindowUtils(window).displayDPI;
|
||||
},
|
||||
|
||||
isPortrait: function isPortrait() {
|
||||
return (window.innerWidth <= window.innerHeight);
|
||||
},
|
||||
|
||||
LOCALE_DIR_RTL: -1,
|
||||
LOCALE_DIR_LTR: 1,
|
||||
get localeDir() {
|
||||
// determine browser dir first to know which direction to snap to
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
|
||||
return chromeReg.isLocaleRTL("global") ? this.LOCALE_DIR_RTL : this.LOCALE_DIR_LTR;
|
||||
},
|
||||
|
||||
/*
|
||||
* Process utilities
|
||||
*/
|
||||
|
||||
isParentProcess: function isInParentProcess() {
|
||||
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
|
||||
return (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
|
||||
},
|
||||
|
||||
/*
|
||||
* Event utilities
|
||||
*/
|
||||
|
||||
modifierMaskFromEvent: function modifierMaskFromEvent(aEvent) {
|
||||
return (aEvent.altKey ? Ci.nsIDOMEvent.ALT_MASK : 0) |
|
||||
(aEvent.ctrlKey ? Ci.nsIDOMEvent.CONTROL_MASK : 0) |
|
||||
(aEvent.shiftKey ? Ci.nsIDOMEvent.SHIFT_MASK : 0) |
|
||||
(aEvent.metaKey ? Ci.nsIDOMEvent.META_MASK : 0);
|
||||
},
|
||||
|
||||
/*
|
||||
* Download utilities
|
||||
*/
|
||||
|
||||
insertDownload: function insertDownload(aSrcUri, aFile) {
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
let db = dm.DBConnection;
|
||||
|
||||
let stmt = db.createStatement(
|
||||
"INSERT INTO moz_downloads (name, source, target, startTime, endTime, state, referrer) " +
|
||||
"VALUES (:name, :source, :target, :startTime, :endTime, :state, :referrer)"
|
||||
);
|
||||
|
||||
stmt.params.name = aFile.leafName;
|
||||
stmt.params.source = aSrcUri.spec;
|
||||
stmt.params.target = aFile.path;
|
||||
stmt.params.startTime = Date.now() * 1000;
|
||||
stmt.params.endTime = Date.now() * 1000;
|
||||
stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
|
||||
stmt.params.referrer = aSrcUri.spec;
|
||||
|
||||
stmt.execute();
|
||||
stmt.finalize();
|
||||
|
||||
let newItemId = db.lastInsertRowID;
|
||||
let download = dm.getDownload(newItemId);
|
||||
//dm.resumeDownload(download);
|
||||
//Services.obs.notifyObservers(download, "dl-start", null);
|
||||
},
|
||||
|
||||
/*
|
||||
* Local system utilities
|
||||
*/
|
||||
|
||||
createShortcut: function Util_createShortcut(aTitle, aURL, aIconURL, aType) {
|
||||
// The background images are 72px, but Android will resize as needed.
|
||||
// Bigger is better than too small.
|
||||
const kIconSize = 72;
|
||||
const kOverlaySize = 32;
|
||||
const kOffset = 20;
|
||||
|
||||
// We have to fallback to something
|
||||
aTitle = aTitle || aURL;
|
||||
|
||||
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
canvas.setAttribute("style", "display: none");
|
||||
|
||||
function _createShortcut() {
|
||||
let icon = canvas.toDataURL("image/png", "");
|
||||
canvas = null;
|
||||
try {
|
||||
let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
|
||||
shell.createShortcut(aTitle, aURL, icon, aType);
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the main background image first
|
||||
let image = new Image();
|
||||
image.onload = function() {
|
||||
canvas.width = canvas.height = kIconSize;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0, kIconSize, kIconSize);
|
||||
|
||||
// If we have a favicon, lets draw it next
|
||||
if (aIconURL) {
|
||||
let favicon = new Image();
|
||||
favicon.onload = function() {
|
||||
// Center the favicon and overlay it on the background
|
||||
ctx.drawImage(favicon, kOffset, kOffset, kOverlaySize, kOverlaySize);
|
||||
_createShortcut();
|
||||
}
|
||||
|
||||
favicon.onerror = function() {
|
||||
Cu.reportError("CreateShortcut: favicon image load error");
|
||||
}
|
||||
|
||||
favicon.src = aIconURL;
|
||||
} else {
|
||||
_createShortcut();
|
||||
}
|
||||
}
|
||||
|
||||
image.onerror = function() {
|
||||
Cu.reportError("CreateShortcut: background image load error");
|
||||
}
|
||||
|
||||
// Pick the right background
|
||||
image.src = aIconURL ? "chrome://browser/skin/images/homescreen-blank-hdpi.png"
|
||||
: "chrome://browser/skin/images/homescreen-default-hdpi.png";
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Timeout
|
||||
*
|
||||
* Helper class to nsITimer that adds a little more pizazz. Callback can be an
|
||||
* object with a notify method or a function.
|
||||
*/
|
||||
Util.Timeout = function(aCallback) {
|
||||
this._callback = aCallback;
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._type = null;
|
||||
};
|
||||
|
||||
Util.Timeout.prototype = {
|
||||
// Timer callback. Don't call this manually.
|
||||
notify: function notify() {
|
||||
if (this._type == this._timer.TYPE_ONE_SHOT)
|
||||
this._type = null;
|
||||
|
||||
if (this._callback.notify)
|
||||
this._callback.notify();
|
||||
else
|
||||
this._callback.apply(null);
|
||||
},
|
||||
|
||||
// Helper function for once and interval.
|
||||
_start: function _start(aDelay, aType, aCallback) {
|
||||
if (aCallback)
|
||||
this._callback = aCallback;
|
||||
this.clear();
|
||||
this._timer.initWithCallback(this, aDelay, aType);
|
||||
this._type = aType;
|
||||
return this;
|
||||
},
|
||||
|
||||
// Do the callback once. Cancels other timeouts on this object.
|
||||
once: function once(aDelay, aCallback) {
|
||||
return this._start(aDelay, this._timer.TYPE_ONE_SHOT, aCallback);
|
||||
},
|
||||
|
||||
// Do the callback every aDelay msecs. Cancels other timeouts on this object.
|
||||
interval: function interval(aDelay, aCallback) {
|
||||
return this._start(aDelay, this._timer.TYPE_REPEATING_SLACK, aCallback);
|
||||
},
|
||||
|
||||
// Clear any pending timeouts.
|
||||
clear: function clear() {
|
||||
if (this.isPending()) {
|
||||
this._timer.cancel();
|
||||
this._type = null;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// If there is a pending timeout, call it and cancel the timeout.
|
||||
flush: function flush() {
|
||||
if (this.isPending()) {
|
||||
this.notify();
|
||||
this.clear();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// Return true if we are waiting for a callback.
|
||||
isPending: function isPending() {
|
||||
return this._type !== null;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/* 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/. */
|
||||
|
||||
// Progress heartbeat timer duration (ms)
|
||||
const kHeartbeatDuration = 1000;
|
||||
// Start and end progress screen css margins as percentages
|
||||
const kProgressMarginStart = 30;
|
||||
const kProgressMarginEnd = 70;
|
||||
|
||||
const WebProgress = {
|
||||
_progressActive: false,
|
||||
|
||||
init: function init() {
|
||||
messageManager.addMessageListener("Content:StateChange", this);
|
||||
messageManager.addMessageListener("Content:LocationChange", this);
|
||||
messageManager.addMessageListener("Content:SecurityChange", this);
|
||||
Elements.progress.addEventListener("transitionend", this._progressTransEnd, true);
|
||||
return this;
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
let json = aMessage.json;
|
||||
let tab = Browser.getTabForBrowser(aMessage.target);
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "Content:StateChange": {
|
||||
if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START)
|
||||
this._windowStart(json, tab);
|
||||
else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP)
|
||||
this._windowStop(json, tab);
|
||||
}
|
||||
|
||||
if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
|
||||
if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START)
|
||||
this._networkStart(json, tab);
|
||||
else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP)
|
||||
this._networkStop(json, tab);
|
||||
}
|
||||
|
||||
this._progressStep();
|
||||
break;
|
||||
}
|
||||
|
||||
case "Content:LocationChange": {
|
||||
this._locationChange(json, tab);
|
||||
this._progressStep();
|
||||
break;
|
||||
}
|
||||
|
||||
case "Content:SecurityChange": {
|
||||
this._securityChange(json, tab);
|
||||
this._progressStep();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_securityChange: function _securityChange(aJson, aTab) {
|
||||
// Don't need to do anything if the data we use to update the UI hasn't changed
|
||||
if (aTab.state == aJson.state && !aTab.hostChanged)
|
||||
return;
|
||||
|
||||
aTab.hostChanged = false;
|
||||
aTab.state = aJson.state;
|
||||
|
||||
if (aTab == Browser.selectedTab) {
|
||||
IdentityUI.checkIdentity();
|
||||
}
|
||||
},
|
||||
|
||||
_locationChange: function _locationChange(aJson, aTab) {
|
||||
let spec = aJson.location;
|
||||
let location = spec.split("#")[0]; // Ignore fragment identifier changes.
|
||||
|
||||
if (aTab == Browser.selectedTab)
|
||||
BrowserUI.updateURI();
|
||||
|
||||
let locationHasChanged = (location != aTab.browser.lastLocation);
|
||||
if (locationHasChanged) {
|
||||
Browser.getNotificationBox(aTab.browser).removeTransientNotifications();
|
||||
aTab.resetZoomLevel();
|
||||
aTab.hostChanged = true;
|
||||
aTab.browser.lastLocation = location;
|
||||
aTab.browser.userTypedValue = "";
|
||||
aTab.browser.appIcon = { href: null, size:-1 };
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
if (CrashReporter.enabled)
|
||||
CrashReporter.annotateCrashReport("URL", spec);
|
||||
#endif
|
||||
this._waitForLoad(aTab);
|
||||
}
|
||||
|
||||
let event = document.createEvent("UIEvents");
|
||||
event.initUIEvent("URLChanged", true, false, window, locationHasChanged);
|
||||
aTab.browser.dispatchEvent(event);
|
||||
},
|
||||
|
||||
_waitForLoad: function _waitForLoad(aTab) {
|
||||
let browser = aTab.browser;
|
||||
|
||||
aTab._firstPaint = false;
|
||||
|
||||
browser.messageManager.addMessageListener("Browser:FirstPaint", function firstPaintListener(aMessage) {
|
||||
browser.messageManager.removeMessageListener(aMessage.name, arguments.callee);
|
||||
aTab._firstPaint = true;
|
||||
aTab.scrolledAreaChanged(true);
|
||||
aTab.updateThumbnailSource();
|
||||
});
|
||||
},
|
||||
|
||||
_networkStart: function _networkStart(aJson, aTab) {
|
||||
aTab.startLoading();
|
||||
|
||||
if (aTab == Browser.selectedTab) {
|
||||
BrowserUI.update(TOOLBARSTATE_LOADING);
|
||||
|
||||
// We should at least show something in the URLBar until
|
||||
// the load has progressed further along
|
||||
if (aTab.browser.currentURI.spec == "about:blank")
|
||||
BrowserUI.updateURI({ captionOnly: true });
|
||||
}
|
||||
},
|
||||
|
||||
_networkStop: function _networkStop(aJson, aTab) {
|
||||
aTab.endLoading();
|
||||
|
||||
if (aTab == Browser.selectedTab) {
|
||||
BrowserUI.update(TOOLBARSTATE_LOADED);
|
||||
}
|
||||
},
|
||||
|
||||
_windowStart: function _windowStart(aJson, aTab) {
|
||||
this._progressStart(aJson, aTab);
|
||||
},
|
||||
|
||||
_windowStop: function _windowStop(aJson, aTab) {
|
||||
this._progressStop(aJson, aTab);
|
||||
},
|
||||
|
||||
_progressStart: function _progressStart(aJson, aTab) {
|
||||
// We will get multiple calls from _windowStart, so
|
||||
// only process once.
|
||||
if (this._progressActive)
|
||||
return;
|
||||
|
||||
this._progressActive = true;
|
||||
|
||||
// 'Whoosh' in
|
||||
this._progressCount = kProgressMarginStart;
|
||||
Elements.progress.style.width = this._progressCount + "%";
|
||||
Elements.progress.removeAttribute("fade");
|
||||
|
||||
// Create a pulse timer to keep things moving even if we don't
|
||||
// collect any state changes.
|
||||
setTimeout(function() {
|
||||
WebProgress._progressStepTimer();
|
||||
}, kHeartbeatDuration, this);
|
||||
},
|
||||
|
||||
_stepProgressCount: function _stepProgressCount() {
|
||||
// Step toward the end margin in smaller slices as we get closer
|
||||
let left = kProgressMarginEnd - this._progressCount;
|
||||
let step = left * .05;
|
||||
this._progressCount += Math.ceil(step);
|
||||
|
||||
// Don't go past the 'whoosh out' margin.
|
||||
if (this._progressCount > kProgressMarginEnd) {
|
||||
this._progressCount = kProgressMarginEnd;
|
||||
}
|
||||
},
|
||||
|
||||
_progressStep: function _progressStep() {
|
||||
if (!this._progressActive)
|
||||
return;
|
||||
this._stepProgressCount();
|
||||
Elements.progress.style.width = this._progressCount + "%";
|
||||
},
|
||||
|
||||
_progressStepTimer: function _progressStepTimer() {
|
||||
if (!this._progressActive)
|
||||
return;
|
||||
this._progressStep();
|
||||
|
||||
setTimeout(function() {
|
||||
WebProgress._progressStepTimer();
|
||||
}, kHeartbeatDuration, this);
|
||||
},
|
||||
|
||||
_progressStop: function _progressStop(aJson, aTab) {
|
||||
this._progressActive = false;
|
||||
// 'Whoosh out' and fade
|
||||
Elements.progress.style.width = "100%";
|
||||
Elements.progress.setAttribute("fade", true);
|
||||
},
|
||||
|
||||
_progressTransEnd: function _progressTransEnd(data) {
|
||||
if (!Elements.progress.hasAttribute("fade"))
|
||||
return;
|
||||
// Close out fade finished, reset
|
||||
if (data.propertyName == "opacity") {
|
||||
Elements.progress.style.width = "0px";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
var Appbar = {
|
||||
get appbar() { return document.getElementById('appbar'); },
|
||||
get consoleButton() { return document.getElementById('console-button'); },
|
||||
get jsShellButton() { return document.getElementById('jsshell-button'); },
|
||||
get zoomInButton() { return document.getElementById('zoomin-button'); },
|
||||
get zoomOutButton() { return document.getElementById('zoomout-button'); },
|
||||
get starButton() { return document.getElementById('star-button'); },
|
||||
get pinButton() { return document.getElementById('pin-button'); },
|
||||
get moreButton() { return document.getElementById('more-button'); },
|
||||
|
||||
// track selected/active richgrid/tilegroup - the context for contextual action buttons
|
||||
activeTileset: null,
|
||||
|
||||
init: function Appbar_init() {
|
||||
window.addEventListener('MozContextUIShow', this, false);
|
||||
window.addEventListener('MozPrecisePointer', this, false);
|
||||
window.addEventListener('MozImprecisePointer', this, false);
|
||||
|
||||
this._updateDebugButtons();
|
||||
this._updateZoomButtons();
|
||||
|
||||
// tilegroup selection events for all modules get bubbled up
|
||||
window.addEventListener("selectionchange", this, false);
|
||||
},
|
||||
|
||||
handleEvent: function Appbar_handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case 'MozContextUIShow':
|
||||
this._updatePinButton();
|
||||
this._updateStarButton();
|
||||
break;
|
||||
case 'MozPrecisePointer':
|
||||
case 'MozImprecisePointer':
|
||||
this._updateZoomButtons();
|
||||
break;
|
||||
case "selectionchange":
|
||||
let nodeName = aEvent.target.nodeName;
|
||||
if ('richgrid' === nodeName) {
|
||||
this._onTileSelectionChanged(aEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadButton: function() {
|
||||
PanelUI.show("downloads-container");
|
||||
ContextUI.dismiss();
|
||||
},
|
||||
|
||||
onZoomOutButton: function() {
|
||||
Browser.zoom(1);
|
||||
},
|
||||
|
||||
onZoomInButton: function() {
|
||||
Browser.zoom(-1);
|
||||
},
|
||||
|
||||
onPinButton: function() {
|
||||
if (this.pinButton.checked) {
|
||||
Browser.pinSite();
|
||||
} else {
|
||||
Browser.unpinSite();
|
||||
}
|
||||
},
|
||||
|
||||
onStarButton: function(aValue) {
|
||||
if (aValue === undefined) {
|
||||
aValue = this.starButton.checked;
|
||||
}
|
||||
|
||||
if (aValue) {
|
||||
Browser.starSite(function () {
|
||||
Appbar._updateStarButton();
|
||||
});
|
||||
} else {
|
||||
Browser.unstarSite(function () {
|
||||
Appbar._updateStarButton();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onMoreButton: function(aEvent) {
|
||||
var typesArray = ["find-in-page"];
|
||||
try {
|
||||
// If we have a valid http or https URI then show the view on desktop
|
||||
// menu item.
|
||||
var uri = Services.io.newURI(Browser.selectedBrowser.currentURI.spec,
|
||||
null, null);
|
||||
if (uri.schemeIs('http') || uri.schemeIs('https')) {
|
||||
typesArray.push("view-on-desktop");
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
|
||||
var x = this.moreButton.getBoundingClientRect().left;
|
||||
var y = this.appbar.getBoundingClientRect().top;
|
||||
ContextMenuUI.showContextMenu({
|
||||
json: {
|
||||
types: typesArray,
|
||||
string: '',
|
||||
xPos: x,
|
||||
yPos: y,
|
||||
forcePosition: true,
|
||||
leftAligned: true,
|
||||
bottomAligned: true
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
onViewOnDesktop: function() {
|
||||
try {
|
||||
// Make sure we have a valid URI so Windows doesn't prompt
|
||||
// with an unrecognized command, select default program window
|
||||
var uri = Services.io.newURI(Browser.selectedBrowser.currentURI.spec,
|
||||
null, null);
|
||||
if (uri.schemeIs('http') || uri.schemeIs('https')) {
|
||||
MetroUtils.launchInDesktop(Browser.selectedBrowser.currentURI.spec, "");
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
},
|
||||
|
||||
onConsoleButton: function() {
|
||||
PanelUI.show("console-container");
|
||||
},
|
||||
|
||||
onJSShellButton: function() {
|
||||
// XXX for debugging, this only works when running on the desktop.
|
||||
if (!MetroUtils.immersive)
|
||||
window.openDialog("chrome://browser/content/shell.xul", "_blank",
|
||||
"all=no,scrollbars=yes,resizable=yes,dialog=no");
|
||||
},
|
||||
|
||||
dispatchContextualAction: function(aActionName){
|
||||
let activeTileset = this.activeTileset;
|
||||
if (activeTileset) {
|
||||
// fire event on the richgrid, others can listen
|
||||
// but we keep coupling loose so grid doesn't need to know about appbar
|
||||
let event = document.createEvent("Events");
|
||||
event.action = aActionName;
|
||||
event.initEvent("context-action", true, false);
|
||||
activeTileset.dispatchEvent(event);
|
||||
|
||||
// done with this selection, explicitly clear it
|
||||
activeTileset.clearSelection();
|
||||
}
|
||||
this.appbar.dismiss();
|
||||
},
|
||||
|
||||
showContextualActions: function(aVerbs){
|
||||
let doc = document;
|
||||
|
||||
// button element id to action verb lookup
|
||||
let buttonsMap = new Map();
|
||||
for (let verb of aVerbs) {
|
||||
let id = verb + "-selected-button";
|
||||
let buttonNode = doc.getElementById(id);
|
||||
if (buttonNode) {
|
||||
buttonsMap.set(id, verb);
|
||||
} else {
|
||||
Util.dumpLn("Appbar.showContextualActions: no button for " + verb);
|
||||
}
|
||||
}
|
||||
|
||||
// hide/show buttons as appropriate
|
||||
let buttons = doc.querySelectorAll("#contextualactions-tray > toolbarbutton");
|
||||
|
||||
for (let btnNode of buttons) {
|
||||
if (buttonsMap.has(btnNode.id)){
|
||||
btnNode.hidden = false;
|
||||
} else {
|
||||
btnNode.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (buttonsMap.size) {
|
||||
// there are buttons to show
|
||||
// TODO: show the contextual actions tray?
|
||||
} else {
|
||||
// 0 actions to show;
|
||||
// TODO: hide the contextual actions tray entirely?
|
||||
}
|
||||
},
|
||||
|
||||
_onTileSelectionChanged: function _onTileSelectionChanged(aEvent){
|
||||
let activeTileset = aEvent.target;
|
||||
|
||||
// deselect tiles in other tile groups
|
||||
if (this.activeTileset && this.activeTileset !== activeTileset) {
|
||||
this.activeTileset.clearSelection();
|
||||
}
|
||||
// keep track of which view is the target/scope for the contextual actions
|
||||
this.activeTileset = activeTileset;
|
||||
|
||||
// ask the view for the list verbs/action-names it thinks are
|
||||
// appropriate for the tiles selected
|
||||
let contextActions = activeTileset.contextActions;
|
||||
let verbs = [v for (v of contextActions)];
|
||||
|
||||
// could transition in old, new buttons?
|
||||
this.showContextualActions(verbs);
|
||||
|
||||
if (verbs.length) {
|
||||
this.appbar.show();
|
||||
} else {
|
||||
this.appbar.dismiss();
|
||||
}
|
||||
},
|
||||
|
||||
_updatePinButton: function() {
|
||||
this.pinButton.checked = Browser.isSitePinned();
|
||||
},
|
||||
|
||||
_updateStarButton: function() {
|
||||
Browser.isSiteStarredAsync(function (isStarred) {
|
||||
this.starButton.checked = isStarred;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_updateDebugButtons: function() {
|
||||
this.consoleButton.disabled = !ConsolePanelView.enabled;
|
||||
this.jsShellButton.disabled = MetroUtils.immersive;
|
||||
},
|
||||
|
||||
_updateZoomButtons: function() {
|
||||
let zoomDisabled = !InputSourceHelper.isPrecise;
|
||||
this.zoomOutButton.disabled = this.zoomInButton.disabled = zoomDisabled;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<binding id="appbarBinding">
|
||||
<content>
|
||||
<xul:toolbar anonid="toolbar"><children/></xul:toolbar>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
window.addEventListener('MozContextUIShow', this);
|
||||
window.addEventListener('MozContextUIDismiss', this);
|
||||
window.addEventListener('MozAppbarDismiss', this);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
window.removeEventListener('MozContextUIShow', this);
|
||||
window.removeEventListener('MozContextUIDismiss', this);
|
||||
window.removeEventListener('MozAppbarDismiss', this);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<field name="sticky">false</field>
|
||||
<field name="_toolbar" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "toolbar");</field>
|
||||
|
||||
<property name="isShowing" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.getAttribute("visible") == "true";
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="dismiss">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.removeAttribute("visible");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="show">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.setAttribute("visible", "true");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="toggle">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.getAttribute("visible") === "true") {
|
||||
this.dismiss();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
switch (aEvent.type) {
|
||||
case 'MozContextUIShow':
|
||||
this.show();
|
||||
break;
|
||||
case 'MozAppbarDismiss':
|
||||
case 'MozContextUIDismiss':
|
||||
this.dismiss();
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<!-- Work around for bug 835175 -->
|
||||
<handler event="click">false;</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,274 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="arrowbox" extends="xul:box">
|
||||
<content orient="vertical">
|
||||
<xul:box anonid="container" class="panel-arrowcontainer" flex="1">
|
||||
<xul:box anonid="arrowbox" class="panel-arrowbox" dir="ltr">
|
||||
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="type"/>
|
||||
</xul:box>
|
||||
<xul:scrollbox anonid="arrowcontent" class="panel-arrowcontent" flex="1">
|
||||
<xul:box class="panel-inner-arrowcontent" xbl:inherits="align,dir,orient,pack,flex">
|
||||
<children/>
|
||||
</xul:box>
|
||||
</xul:scrollbox>
|
||||
</xul:box>
|
||||
</content>
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
window.addEventListener("resize", this._eventHandler, false);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
window.removeEventListener("resize", this._eventHandler, false);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<field name="anonScrollBox" readonly="true"><![CDATA[
|
||||
// Expose the anyonymous scrollbox so ScrollUtils.getScrollboxFromElement can find it.
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "arrowcontent");
|
||||
]]></field>
|
||||
|
||||
<property name="offset" onget="return parseInt(this.getAttribute('offset')) || 0;"
|
||||
onset="this.setAttribute('offset', val); return val;"/>
|
||||
|
||||
<method name="_updateArrow">
|
||||
<parameter name="popupRect"/>
|
||||
<parameter name="targetRect"/>
|
||||
<parameter name="horizPos"/>
|
||||
<parameter name="vertPos"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
|
||||
if (!popupRect || !targetRect) {
|
||||
arrow.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let container = document.getAnonymousElementByAttribute(this, "anonid", "container");
|
||||
let content = document.getAnonymousElementByAttribute(this, "anonid", "arrowcontent");
|
||||
let arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
|
||||
|
||||
// If the content of the arrowbox if taller than the available
|
||||
// screen space, force a maximum height
|
||||
this.style.minHeight = "";
|
||||
content.style.overflow = "visible";
|
||||
const kBottomMargin = 64;
|
||||
let contentRect = content.firstChild.getBoundingClientRect();
|
||||
if ((contentRect.height + contentRect.top + kBottomMargin) > window.innerHeight) {
|
||||
content.style.overflow = "hidden";
|
||||
this.style.minHeight = (window.innerHeight - parseInt(this.top) - kBottomMargin) + "px";
|
||||
}
|
||||
|
||||
let HALF_ARROW_WIDTH = 16;
|
||||
|
||||
let anchorClass = "";
|
||||
let hideArrow = false;
|
||||
if (horizPos == 0) {
|
||||
container.orient = "vertical";
|
||||
arrowbox.orient = "";
|
||||
if (vertPos == 0) {
|
||||
hideArrow = true;
|
||||
} else {
|
||||
let anchorPosX = 0.5;
|
||||
// check for hasAttribute because, in some cases, anchorNode is actually a rect
|
||||
if (this.anchorNode && this.anchorNode.hasAttribute && this.anchorNode.hasAttribute("anchorPosX"))
|
||||
anchorPosX = parseFloat(this.anchorNode.getAttribute("anchorPosX")) || 0.5;
|
||||
arrowbox.style.marginLeft = ((targetRect.left - popupRect.left) + (targetRect.width * anchorPosX) - HALF_ARROW_WIDTH) + "px";
|
||||
if (vertPos == 1) {
|
||||
container.dir = "normal";
|
||||
anchorClass = "top";
|
||||
} else if (vertPos == -1) {
|
||||
container.dir = "reverse";
|
||||
anchorClass = "bottom";
|
||||
}
|
||||
}
|
||||
} else if (vertPos == 0) {
|
||||
container.orient = "";
|
||||
arrowbox.orient = "vertical";
|
||||
let anchorPosY = 0.5;
|
||||
// check for hasAttribute because, in some cases, anchorNode is actually a rect
|
||||
if (this.anchorNode && this.anchorNode.hasAttribute && this.anchorNode.hasAttribute("anchorPosY"))
|
||||
anchorPosY = parseFloat(this.anchorNode.getAttribute("anchorPosY")) || 0.5;
|
||||
arrowbox.style.marginTop = ((targetRect.top - popupRect.top) + (targetRect.height * anchorPosY) - HALF_ARROW_WIDTH) + "px";
|
||||
if (horizPos == 1) {
|
||||
container.dir = "ltr";
|
||||
anchorClass = "left";
|
||||
} else if (horizPos == -1) {
|
||||
container.dir = "rtl";
|
||||
anchorClass = "right";
|
||||
}
|
||||
} else {
|
||||
hideArrow = true;
|
||||
}
|
||||
arrow.hidden = hideArrow;
|
||||
arrow.setAttribute("side", anchorClass);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<field name="anchorNode">null</field>
|
||||
<method name="anchorTo">
|
||||
<parameter name="aAnchorNode"/>
|
||||
<parameter name="aPosition"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!aAnchorNode) {
|
||||
this._updateArrow(null, null, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.anchorNode = aAnchorNode;
|
||||
this.position = aPosition;
|
||||
|
||||
let anchorRect = aAnchorNode.getBoundingClientRect();
|
||||
let popupRect = new Rect(0,0,0,0);
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
popupRect.expandToContain(Rect.fromRect(this.childNodes[i].getBoundingClientRect()));
|
||||
}
|
||||
let offset = this.offset;
|
||||
let horizPos = 0;
|
||||
let vertPos = 0;
|
||||
|
||||
if (aPosition) {
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
|
||||
let isRtl = chromeReg.isLocaleRTL("global");
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
|
||||
switch (aPosition) {
|
||||
case "before_start":
|
||||
left = isRtl ? anchorRect.right - popupRect.width : anchorRect.left;
|
||||
top = anchorRect.top + offset - popupRect.height;
|
||||
vertPos = -1;
|
||||
break;
|
||||
case "before_end":
|
||||
left = isRtl ? anchorRect.left : anchorRect.right - popupRect.width;
|
||||
top = anchorRect.top + offset - popupRect.height;
|
||||
vertPos = -1;
|
||||
break;
|
||||
case "after_start":
|
||||
left = isRtl ? anchorRect.right - popupRect.width : anchorRect.left;
|
||||
top = anchorRect.bottom - offset;
|
||||
vertPos = 1;
|
||||
break;
|
||||
case "after_end":
|
||||
left = isRtl ? anchorRect.left : anchorRect.right - popupRect.width;
|
||||
top = anchorRect.bottom - offset;
|
||||
vertPos = 1;
|
||||
break;
|
||||
case "start_before":
|
||||
left = isRtl ? anchorRect.right : anchorRect.left - popupRect.width - offset;
|
||||
top = anchorRect.top;
|
||||
horizPos = -1;
|
||||
break;
|
||||
case "start_after":
|
||||
left = isRtl ? anchorRect.right : anchorRect.left - popupRect.width - offset;
|
||||
top = anchorRect.bottom - popupRect.height;
|
||||
horizPos = -1;
|
||||
break;
|
||||
case "end_before":
|
||||
left = isRtl ? anchorRect.left - popupRect.width - offset : anchorRect.right;
|
||||
top = anchorRect.top;
|
||||
horizPos = 1;
|
||||
break;
|
||||
case "end_after":
|
||||
left = isRtl ? anchorRect.left - popupRect.width - offset : anchorRect.right;
|
||||
top = anchorRect.bottom - popupRect.height;
|
||||
horizPos = 1;
|
||||
break;
|
||||
case "overlap":
|
||||
left = isRtl ? anchorRect.right - popupRect.width + offset : anchorRect.left + offset ;
|
||||
top = anchorRect.top + offset ;
|
||||
break;
|
||||
}
|
||||
if (top == 0) top = 1;
|
||||
if (left == 0) left = 1;
|
||||
|
||||
if (left + popupRect.width > window.innerWidth)
|
||||
left = window.innerWidth - popupRect.width;
|
||||
else if (left < 0)
|
||||
left = 1;
|
||||
|
||||
popupRect.left = left;
|
||||
this.setAttribute("left", left);
|
||||
popupRect.top = top;
|
||||
this.setAttribute("top", top);
|
||||
} else {
|
||||
horizPos = (Math.round(popupRect.right) <= Math.round(anchorRect.left + offset)) ? -1 :
|
||||
(Math.round(popupRect.left) >= Math.round(anchorRect.right - offset)) ? 1 : 0;
|
||||
vertPos = (Math.round(popupRect.bottom) <= Math.round(anchorRect.top + offset)) ? -1 :
|
||||
(Math.round(popupRect.top) >= Math.round(anchorRect.bottom - offset)) ? 1 : 0;
|
||||
}
|
||||
this._updateArrow(popupRect, anchorRect, horizPos, vertPos);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="pointLeftAt">
|
||||
<parameter name="targetNode"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!targetNode) {
|
||||
this._updateArrow(null, null, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
let popupRect = this.getBoundingClientRect();
|
||||
let targetRect = targetNode.getBoundingClientRect();
|
||||
this._updateArrow(popupRect, targetRect, 1, 0);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="pointRightAt">
|
||||
<parameter name="targetNode"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!targetNode) {
|
||||
this._updateArrow(null, null, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
let popupRect = this.getBoundingClientRect();
|
||||
let targetRect = targetNode.getBoundingClientRect();
|
||||
|
||||
this._updateArrow(popupRect, targetRect, -1, 0);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_eventHandler"><![CDATA[
|
||||
({
|
||||
self: this,
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
// We need to reset the margins because the previous values could
|
||||
// cause the arrowbox to size incorrectly.
|
||||
let self = this.self;
|
||||
switch (aEvent.type) {
|
||||
case "resize":
|
||||
// Do nothing if there's no anchorNode
|
||||
if (!self.anchorNode)
|
||||
break;
|
||||
let arrowbox = document.getAnonymousElementByAttribute(self, "anonid", "arrowbox");
|
||||
arrowbox.style.marginLeft = "0px";
|
||||
arrowbox.style.marginTop = "0px";
|
||||
self.anchorTo(self.anchorNode, self.position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
]]></field>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,445 @@
|
|||
<?xml version="1.0" encoding="Windows-1252" ?>
|
||||
<!-- 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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="autocomplete" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.minResultsForPopup = 0;
|
||||
this.popup._input = this;
|
||||
]]>
|
||||
</constructor>
|
||||
<method name="openPopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.openAutocompletePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.closePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="dblclick" phase="capturing">
|
||||
<![CDATA[
|
||||
let selectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
|
||||
if (selectAll)
|
||||
this.select();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, true);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="keypress" phase="capturing" keycode="VK_RETURN">
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.popup.handleCompletion();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="autocomplete-popup">
|
||||
<content orient="horizontal">
|
||||
<xul:vbox id="results-vbox" class="meta-section viewable-height">
|
||||
<xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
|
||||
<richgrid id="results-richgrid" anonid="results" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
|
||||
<xul:vbox id="searches-vbox" class="meta-section viewable-height">
|
||||
<xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
|
||||
<richgrid id="searches-richgrid" anonid="searches" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIAutoCompletePopup, nsIObserver">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified", false);
|
||||
this.updateSearchEngines();
|
||||
this._results.controller = this;
|
||||
this._searches.controller = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<method name="handleItemClick">
|
||||
<parameter name="aItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aItem.control == this._searches) {
|
||||
let engineName = aItem.getAttribute("value");
|
||||
BrowserUI.doOpenSearch(engineName);
|
||||
} else {
|
||||
let url = aItem.getAttribute("value");
|
||||
Browser.loadURI(url);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIAutocompleteInput -->
|
||||
|
||||
<field name="_input">null</field>
|
||||
<field name="_popupOpen">false</field>
|
||||
|
||||
<property name="overrideValue" readonly="true" onget="return null;"/>
|
||||
|
||||
<property name="selectedItem">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
<property name="selectedIndex">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="input" readonly="true" onget="return this._input;"/>
|
||||
<property name="popupOpen" readonly="true" onget="return this._popupOpen;"/>
|
||||
<property name="_matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
|
||||
|
||||
<method name="openAutocompletePopup">
|
||||
<parameter name="aInput"/>
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._popupOpen)
|
||||
return;
|
||||
|
||||
ContextUI.dismissAppbar();
|
||||
|
||||
this._input = aInput;
|
||||
this._popupOpen = true;
|
||||
this._grid = this._results;
|
||||
|
||||
this.clearSelection();
|
||||
this.invalidate();
|
||||
this._fire("autocompletestart");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="gridBoundCallback">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.updateResults();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._popupOpen)
|
||||
return;
|
||||
|
||||
this.input.controller.stopSearch();
|
||||
this._popupOpen = false;
|
||||
this._fire("autocompleteend");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="invalidate">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._popupOpen)
|
||||
return;
|
||||
|
||||
this.updateResults();
|
||||
this.updateSearchEngineSubtitles();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="selectBy">
|
||||
<parameter name="aReverse"/>
|
||||
<parameter name="aPage"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let handleOnSelect = this._handleOnSelect;
|
||||
this._handleOnSelect = false;
|
||||
|
||||
// TODO <jwilde>: Pressing page up/down should jump between rows,
|
||||
// not just items in the grid
|
||||
|
||||
// Move between grids if we're at the edge of one
|
||||
if ((this._grid.isSelectionAtEnd && !aReverse) ||
|
||||
(this._grid.isSelectionAtStart && aReverse)) {
|
||||
let index = aReverse ? this._otherGrid.itemCount - 1 : 0;
|
||||
this._otherGrid.selectedIndex = index;
|
||||
} else {
|
||||
this._grid.offsetSelection(aReverse ? -1 : 1);
|
||||
}
|
||||
|
||||
this._handleOnSelect = handleOnSelect;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIObserver -->
|
||||
|
||||
<method name="observe">
|
||||
<parameter name="aSubject"/>
|
||||
<parameter name="aTopic"/>
|
||||
<parameter name="aData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aTopic != "browser-search-engine-modified")
|
||||
return;
|
||||
|
||||
switch (aData) {
|
||||
case "engine-added":
|
||||
case "engine-removed":
|
||||
case "engine-changed":
|
||||
this.updateSearchEngines();
|
||||
break;
|
||||
case "engine-current":
|
||||
// Not relevant
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for updating various components of the popup. -->
|
||||
|
||||
<method name="updateResults">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._results))
|
||||
return;
|
||||
if (!this.input)
|
||||
return;
|
||||
|
||||
let controller = this.input.controller;
|
||||
let lastMatch = this._matchCount - 1;
|
||||
let iterCount = Math.max(this._results.itemCount, this._matchCount);
|
||||
|
||||
// Swap out existing items for new search hit results
|
||||
for (let i = 0; i < iterCount; i++) {
|
||||
if (i > lastMatch) {
|
||||
let lastItem = this._results.itemCount - 1;
|
||||
this._results.removeItemAt(lastItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = controller.getValueAt(i);
|
||||
let label = controller.getCommentAt(i) || value;
|
||||
let iconURI = controller.getImageAt(i);
|
||||
|
||||
let item = this._results.getItemAtIndex(i);
|
||||
if (item == null) {
|
||||
item = this._results.appendItem(label, value);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
} else {
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("value", value);
|
||||
}
|
||||
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}
|
||||
|
||||
this._results.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngines">
|
||||
<body><![CDATA[
|
||||
Services.search.init(this._onSearchServiceInit.bind(this));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_onSearchServiceInit">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
this._engines = Services.search.getVisibleEngines();
|
||||
|
||||
while (this._searches.itemCount > 0)
|
||||
this._searches.removeItemAt(0);
|
||||
|
||||
this._engines.forEach(function (anEngine) {
|
||||
let item = this._searches.appendItem(anEngine.name, anEngine.name);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
item.setAttribute("search", "true");
|
||||
|
||||
let iconURI = anEngine.iconURI ? anEngine.iconURI.spec : "";
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}.bind(this));
|
||||
|
||||
this._searches.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngineSubtitles">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
let searchString = this.input.controller.searchString;
|
||||
let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1);
|
||||
|
||||
for (let i = 0, len = this._searches.itemCount; i < len; i++) {
|
||||
let item = this._searches.getItemAtIndex(i);
|
||||
item.setAttribute("label", label);
|
||||
}
|
||||
|
||||
this._searches.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for handling actions across grids -->
|
||||
|
||||
<method name="handleCompletion">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._grid == this._results) {
|
||||
this.input.controller.handleEnter(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._grid == this._searches) {
|
||||
let engine = this._engines[this._grid.selectedIndex];
|
||||
BrowserUI.doOpenSearch(engine.name);
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearSelection">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._isGridBound(this._results))
|
||||
this._results.clearSelection();
|
||||
|
||||
if (this._isGridBound(this._searches))
|
||||
this._searches.clearSelection();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Helpers -->
|
||||
|
||||
<field name="_engines">[]</field>
|
||||
<field name="_handleOnSelect">true</field>
|
||||
<field name="_grid">null</field>
|
||||
|
||||
<property name="_results" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'results');"/>
|
||||
<property name="_searches" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'searches');"/>
|
||||
|
||||
<property name="_otherGrid" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return (this._grid == this._results) ? this._searches : this._results;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="_isGridBound">
|
||||
<parameter name="aGrid"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return aGrid.itemCount != undefined;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_fire">
|
||||
<parameter name="aName"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent(aName, true, false);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="select">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
if (grid != this._grid) {
|
||||
if (this._grid.clearSelection)
|
||||
this._grid.clearSelection();
|
||||
this._grid = grid;
|
||||
}
|
||||
|
||||
if (this._handleOnSelect && this._results.selectedItem) {
|
||||
BrowserUI.goToURI(
|
||||
this._results.selectedItem.getAttribute("value"));
|
||||
this.closePopup();
|
||||
} else if (this._handleOnSelect) {
|
||||
this.handleCompletion();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="contentgenerated">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
if (grid == this._searches)
|
||||
this.updateSearchEngines();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,407 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<!-- ==================================================================
|
||||
Events: When these elements are in chrome, they are not obscured
|
||||
by the touch input overlay. When they are in content they may be
|
||||
obscured depending on the type of input we receive.
|
||||
chrome: regular mouse events, moz gesture events, tap events.
|
||||
content: touch input - tap events and synthetic mouse events
|
||||
generated by content code. mouse input - regular mouse events,
|
||||
moz gesture events, tap events.
|
||||
================================================================== -->
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="richlistbox-batch" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
|
||||
<handlers>
|
||||
<handler event="scroll">
|
||||
<![CDATA[
|
||||
// if there no more items to insert, just return early
|
||||
if (this._items.length == 0)
|
||||
return;
|
||||
|
||||
if (this._contentScrollHeight == -1) {
|
||||
let scrollheight = {};
|
||||
this.scrollBoxObject.getScrolledSize({}, scrollheight);
|
||||
this._contentScrollHeight = scrollheight.value;
|
||||
}
|
||||
|
||||
let y = {};
|
||||
this.scrollBoxObject.getPosition({}, y);
|
||||
let scrollRatio = (y.value + this._childrenHeight) / this._contentScrollHeight;
|
||||
|
||||
// If we're scrolled 80% to the bottom of the list, append the next
|
||||
// set of items
|
||||
if (scrollRatio > 0.8)
|
||||
this._insertItems();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
<implementation>
|
||||
<!-- Number of elements to add to the list initially. If there are more
|
||||
than this many elements to display, only add them to the list once
|
||||
the user has scrolled towards them. This is a performance
|
||||
optimization to avoid locking up while attempting to append hundreds
|
||||
of nodes to our richlistbox.
|
||||
-->
|
||||
<property name="batchSize" readonly="true" onget="return this.getAttribute('batch')"/>
|
||||
|
||||
<field name="_childrenHeight">this.scrollBoxObject.height;</field>
|
||||
<field name="_items">[]</field>
|
||||
|
||||
<method name="setItems">
|
||||
<parameter name="aItems"/>
|
||||
<body><![CDATA[
|
||||
this._items = aItems;
|
||||
this._insertItems();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_insertItems">
|
||||
<body><![CDATA[
|
||||
let items = this._items.splice(0, this.batchSize);
|
||||
if (!items.length)
|
||||
return; // no items to insert
|
||||
|
||||
let count = items.length;
|
||||
for (let i = 0; i<count; i++)
|
||||
this.appendChild(items[i]);
|
||||
|
||||
|
||||
// make sure we recalculate the scrollHeight of the content
|
||||
this._contentScrollHeight = -1;
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="richlistbox-contextmenu" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
|
||||
<handlers>
|
||||
<handler event="click" phase="bubble">
|
||||
<![CDATA[
|
||||
ContextMenuUI.hide();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
|
||||
<handlers>
|
||||
<handler event="mousedown" phase="capturing">
|
||||
<![CDATA[
|
||||
// We'll get this if the user is interacting via the mouse
|
||||
let domUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
|
||||
getService(Ci.inIDOMUtils);
|
||||
domUtils.setContentState(this, 0x00000001);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="click" phase="capturing">
|
||||
<![CDATA[
|
||||
// allow normal mouse event processing
|
||||
if (!InputSourceHelper || InputSourceHelper.isPrecise)
|
||||
return;
|
||||
// trap this here, we'll rely on tap events
|
||||
// event.stopPropagation();
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="touchstart" phase="capturing">
|
||||
<![CDATA[
|
||||
// touch input event
|
||||
let domUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
|
||||
getService(Ci.inIDOMUtils);
|
||||
domUtils.setContentState(this, 0x00000001);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="content-navigator">
|
||||
<content class="window-width content-navigator-box" orient="horizontal" pack="start">
|
||||
<children includes="textbox"/>
|
||||
<xul:button anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
|
||||
<xul:button anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:button anonid="close-button" class="content-navigator-item close-button" xbl:inherits="command=close"/>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<field name="_previousButton">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "previous-button");
|
||||
</field>
|
||||
|
||||
<field name="_nextButton">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "next-button");
|
||||
</field>
|
||||
|
||||
<field name="_closeButton">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "close-button");
|
||||
</field>
|
||||
|
||||
<method name="contentHasChanged">
|
||||
<body><![CDATA[
|
||||
if (!this.isActive)
|
||||
return;
|
||||
|
||||
// There is a race condition with getBoundingClientRect and when the
|
||||
// box is displayed, the padding is ignored in the size calculation.
|
||||
// A nested timeouts below are used to workaround this problem.
|
||||
this.getBoundingClientRect();
|
||||
|
||||
setTimeout(function(self) {
|
||||
setTimeout(function(self) {
|
||||
let height = Math.floor(self.getBoundingClientRect().height);
|
||||
self.top = window.innerHeight - height;
|
||||
}, 0, self);
|
||||
}, 0, this);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="isActive" onget="return !!this.model;"/>
|
||||
|
||||
<field name="model">null</field>
|
||||
<method name="show">
|
||||
<parameter name="aModel" />
|
||||
<body><![CDATA[
|
||||
// call the hide callback of the current object if any
|
||||
if (this.model && this.model.type != aModel.type)
|
||||
this.model.hide();
|
||||
|
||||
this.setAttribute("type", aModel.type);
|
||||
this.setAttribute("next", aModel.commands.next);
|
||||
this.setAttribute("previous", aModel.commands.previous);
|
||||
this.setAttribute("close", aModel.commands.close);
|
||||
|
||||
// buttons attributes sync with commands doesn not look updated when
|
||||
// we dynamically switch the "command" attribute so we need to ensure
|
||||
// the disabled state of the buttons is right when switching commands
|
||||
this._previousButton.disabled = document.getElementById(aModel.commands.previous).getAttribute("disabled") == "true";
|
||||
this._nextButton.disabled = document.getElementById(aModel.commands.next).getAttribute("disabled") == "true";
|
||||
|
||||
this.model = aModel;
|
||||
this.contentHasChanged();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="hide">
|
||||
<parameter name="aModel" />
|
||||
<body><![CDATA[
|
||||
this.removeAttribute("next");
|
||||
this.removeAttribute("previous");
|
||||
this.removeAttribute("close");
|
||||
this.removeAttribute("type");
|
||||
|
||||
this.contentHasChanged();
|
||||
this.model = null;
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="menulist" display="xul:box" extends="chrome://global/content/bindings/menulist.xml#menulist">
|
||||
<handlers>
|
||||
<handler event="mousedown" phase="capturing">
|
||||
<![CDATA[
|
||||
// Stop the normal menupopup from appearing
|
||||
event.stopPropagation();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="click" button="0">
|
||||
<![CDATA[
|
||||
if (this.disabled || this.itemCount == 0)
|
||||
return;
|
||||
|
||||
this.focus();
|
||||
MenuControlUI.show(this);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="command" phase="capturing">
|
||||
<![CDATA[
|
||||
// The dropmark (button) fires a command event too. Don't forward that.
|
||||
// Just forward the menuitem command events, which the toolkit version does.
|
||||
if (event.target.parentNode.parentNode != this)
|
||||
event.stopPropagation();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="chrome-select-option">
|
||||
<content orient="horizontal" flex="1">
|
||||
<xul:image class="chrome-select-option-image" anonid="check"/>
|
||||
<xul:label anonid="label" xbl:inherits="value=label"/>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<property name="selected">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.hasAttribute("selected");
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val)
|
||||
this.setAttribute("selected", "true");
|
||||
else
|
||||
this.removeAttribute("selected");
|
||||
return val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="select-button" extends="xul:box">
|
||||
<content>
|
||||
<svg:svg width="11px" version="1.1" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: -moz-calc(50% - 2px); left: 4px;">
|
||||
<svg:polyline points="1 1 5 6 9 1" stroke="#414141" stroke-width="2" stroke-linecap="round" fill="transparent" stroke-linejoin="round"/>
|
||||
</svg:svg>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
|
||||
<handlers>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, false);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="textarea" extends="chrome://global/content/bindings/textbox.xml#textarea">
|
||||
<handlers>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, false);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="timed-textbox" extends="chrome://global/content/bindings/textbox.xml#timed-textbox">
|
||||
<handlers>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, false);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#search-textbox">
|
||||
<implementation>
|
||||
<field name="_searchClear">
|
||||
<![CDATA[
|
||||
document.getAnonymousElementByAttribute(this, "class", "textbox-search-clear");
|
||||
]]>
|
||||
</field>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, false);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="text" phase="bubbling"><![CDATA[
|
||||
// Listen for composition update, some VKB that does suggestions does not
|
||||
// update directly the content of the field but in this case we want to
|
||||
// search as soon as something is entered in the field
|
||||
let evt = document.createEvent("Event");
|
||||
evt.initEvent("input", true, false);
|
||||
this.dispatchEvent(evt);
|
||||
]]></handler>
|
||||
|
||||
<handler event="click" phase="bubbling"><![CDATA[
|
||||
// bug 629661. To reset the autosuggestions mechanism of Android, the
|
||||
// textfield need to reset the IME state
|
||||
if (event.originalTarget == this._searchClear) {
|
||||
setTimeout(function(self) {
|
||||
try {
|
||||
let imeEditor = self.inputField.QueryInterface(Ci.nsIDOMNSEditableElement)
|
||||
.editor
|
||||
.QueryInterface(Ci.nsIEditorIMESupport);
|
||||
if (imeEditor.composing)
|
||||
imeEditor.forceCompositionEnd();
|
||||
} catch(e) {}
|
||||
}, 0, this);
|
||||
}
|
||||
]]></handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="numberbox" extends="chrome://global/content/bindings/numberbox.xml#numberbox">
|
||||
<handlers>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, false);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="input-box" extends="xul:box">
|
||||
<implementation>
|
||||
<method name="showContextMenu">
|
||||
<parameter name="aTextbox"/>
|
||||
<parameter name="aEvent"/>
|
||||
<parameter name="aIgnoreReadOnly"/>
|
||||
<body><![CDATA[
|
||||
let selectionStart = aTextbox.selectionStart;
|
||||
let selectionEnd = aTextbox.selectionEnd;
|
||||
|
||||
let json = { types: ["input-text"], string: "" };
|
||||
if (selectionStart != selectionEnd) {
|
||||
json.types.push("copy");
|
||||
json.string = aTextbox.value.slice(selectionStart, selectionEnd);
|
||||
} else if (aTextbox.value) {
|
||||
json.types.push("copy-all");
|
||||
json.string = aTextbox.value;
|
||||
}
|
||||
|
||||
if (selectionStart > 0 || selectionEnd < aTextbox.textLength)
|
||||
json.types.push("select-all");
|
||||
|
||||
let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"].
|
||||
getService(Ci.nsIClipboard);
|
||||
let flavors = ["text/unicode"];
|
||||
let hasData = clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard);
|
||||
|
||||
if (hasData && (!aTextbox.readOnly || aIgnoreReadOnly)) {
|
||||
json.types.push("paste");
|
||||
if (aTextbox.type == "url") {
|
||||
json.types.push("paste-url");
|
||||
}
|
||||
}
|
||||
json.xPos = aEvent.clientX;
|
||||
json.yPos = aEvent.clientY;
|
||||
json.source = aEvent.mozInputSource;
|
||||
ContextMenuUI.showContextMenu({ target: aTextbox, json: json });
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,796 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let WebProgressListener = {
|
||||
_lastLocation: null,
|
||||
_firstPaint: false,
|
||||
|
||||
init: function() {
|
||||
let flags = Ci.nsIWebProgress.NOTIFY_LOCATION |
|
||||
Ci.nsIWebProgress.NOTIFY_SECURITY |
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_NETWORK |
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT;
|
||||
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, flags);
|
||||
},
|
||||
|
||||
onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (content != aWebProgress.DOMWindow)
|
||||
return;
|
||||
|
||||
sendAsyncMessage("Content:StateChange", {
|
||||
contentWindowId: this.contentWindowId,
|
||||
stateFlags: aStateFlags,
|
||||
status: aStatus
|
||||
});
|
||||
},
|
||||
|
||||
onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
|
||||
if (content != aWebProgress.DOMWindow)
|
||||
return;
|
||||
|
||||
let spec = aLocationURI ? aLocationURI.spec : "";
|
||||
let location = spec.split("#")[0];
|
||||
|
||||
let charset = content.document.characterSet;
|
||||
|
||||
sendAsyncMessage("Content:LocationChange", {
|
||||
contentWindowId: this.contentWindowId,
|
||||
documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec,
|
||||
location: spec,
|
||||
canGoBack: docShell.canGoBack,
|
||||
canGoForward: docShell.canGoForward,
|
||||
charset: charset.toString()
|
||||
});
|
||||
|
||||
this._firstPaint = false;
|
||||
let self = this;
|
||||
|
||||
// Keep track of hash changes
|
||||
this.hashChanged = (location == this._lastLocation);
|
||||
this._lastLocation = location;
|
||||
|
||||
// When a new page is loaded fire a message for the first paint
|
||||
addEventListener("MozAfterPaint", function(aEvent) {
|
||||
removeEventListener("MozAfterPaint", arguments.callee, true);
|
||||
|
||||
self._firstPaint = true;
|
||||
let scrollOffset = ContentScroll.getScrollOffset(content);
|
||||
sendAsyncMessage("Browser:FirstPaint", scrollOffset);
|
||||
}, true);
|
||||
},
|
||||
|
||||
onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
|
||||
},
|
||||
|
||||
onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
|
||||
if (content != aWebProgress.DOMWindow)
|
||||
return;
|
||||
|
||||
let serialization = SecurityUI.getSSLStatusAsString();
|
||||
|
||||
sendAsyncMessage("Content:SecurityChange", {
|
||||
contentWindowId: this.contentWindowId,
|
||||
SSLStatusAsString: serialization,
|
||||
state: aState
|
||||
});
|
||||
},
|
||||
|
||||
get contentWindowId() {
|
||||
return content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
},
|
||||
|
||||
QueryInterface: function QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIWebProgressListener) ||
|
||||
aIID.equals(Ci.nsISupportsWeakReference) ||
|
||||
aIID.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
WebProgressListener.init();
|
||||
|
||||
|
||||
let SecurityUI = {
|
||||
getSSLStatusAsString: function() {
|
||||
let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
|
||||
|
||||
if (status) {
|
||||
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
|
||||
.getService(Ci.nsISerializationHelper);
|
||||
|
||||
status.QueryInterface(Ci.nsISerializable);
|
||||
return serhelper.serializeToString(status);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
let WebNavigation = {
|
||||
_webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation),
|
||||
_timer: null,
|
||||
|
||||
init: function() {
|
||||
addMessageListener("WebNavigation:GoBack", this);
|
||||
addMessageListener("WebNavigation:GoForward", this);
|
||||
addMessageListener("WebNavigation:GotoIndex", this);
|
||||
addMessageListener("WebNavigation:LoadURI", this);
|
||||
addMessageListener("WebNavigation:Reload", this);
|
||||
addMessageListener("WebNavigation:Stop", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
switch (message.name) {
|
||||
case "WebNavigation:GoBack":
|
||||
this.goBack();
|
||||
break;
|
||||
case "WebNavigation:GoForward":
|
||||
this.goForward();
|
||||
break;
|
||||
case "WebNavigation:GotoIndex":
|
||||
this.gotoIndex(message);
|
||||
break;
|
||||
case "WebNavigation:LoadURI":
|
||||
this.loadURI(message);
|
||||
break;
|
||||
case "WebNavigation:Reload":
|
||||
this.reload(message);
|
||||
break;
|
||||
case "WebNavigation:Stop":
|
||||
this.stop(message);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
goBack: function() {
|
||||
if (this._webNavigation.canGoBack)
|
||||
this._webNavigation.goBack();
|
||||
},
|
||||
|
||||
goForward: function() {
|
||||
if (this._webNavigation.canGoForward)
|
||||
this._webNavigation.goForward();
|
||||
},
|
||||
|
||||
gotoIndex: function(message) {
|
||||
this._webNavigation.gotoIndex(message.index);
|
||||
},
|
||||
|
||||
loadURI: function(message) {
|
||||
let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
|
||||
this._webNavigation.loadURI(message.json.uri, flags, null, null, null);
|
||||
|
||||
let tabData = message.json;
|
||||
if (tabData.entries) {
|
||||
// We are going to load from history so kill the current load. We do not
|
||||
// want the load added to the history anyway. We reload after resetting history
|
||||
this._webNavigation.stop(this._webNavigation.STOP_ALL);
|
||||
this._restoreHistory(tabData, 0);
|
||||
}
|
||||
},
|
||||
|
||||
reload: function(message) {
|
||||
let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
|
||||
this._webNavigation.reload(flags);
|
||||
},
|
||||
|
||||
stop: function(message) {
|
||||
let flags = message.json.flags || this._webNavigation.STOP_ALL;
|
||||
this._webNavigation.stop(flags);
|
||||
},
|
||||
|
||||
_restoreHistory: function _restoreHistory(aTabData, aCount) {
|
||||
// We need to wait for the sessionHistory to be initialized and there
|
||||
// is no good way to do this. We'll try a wait loop like desktop
|
||||
try {
|
||||
if (!this._webNavigation.sessionHistory)
|
||||
throw new Error();
|
||||
} catch (ex) {
|
||||
if (aCount < 10) {
|
||||
let self = this;
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.initWithCallback(function(aTimer) {
|
||||
self._timer = null;
|
||||
self._restoreHistory(aTabData, aCount + 1);
|
||||
}, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let history = this._webNavigation.sessionHistory;
|
||||
if (history.count > 0)
|
||||
history.PurgeHistory(history.count);
|
||||
history.QueryInterface(Ci.nsISHistoryInternal);
|
||||
|
||||
// helper hashes for ensuring unique frame IDs and unique document
|
||||
// identifiers.
|
||||
let idMap = { used: {} };
|
||||
let docIdentMap = {};
|
||||
|
||||
for (let i = 0; i < aTabData.entries.length; i++) {
|
||||
if (!aTabData.entries[i].url)
|
||||
continue;
|
||||
history.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true);
|
||||
}
|
||||
|
||||
// We need to force set the active history item and cause it to reload since
|
||||
// we stop the load above
|
||||
let activeIndex = (aTabData.index || aTabData.entries.length) - 1;
|
||||
history.getEntryAtIndex(activeIndex, true);
|
||||
history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
|
||||
},
|
||||
|
||||
_deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
|
||||
let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry);
|
||||
|
||||
shEntry.setURI(Services.io.newURI(aEntry.url, null, null));
|
||||
shEntry.setTitle(aEntry.title || aEntry.url);
|
||||
if (aEntry.subframe)
|
||||
shEntry.setIsSubFrame(aEntry.subframe || false);
|
||||
shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
|
||||
if (aEntry.contentType)
|
||||
shEntry.contentType = aEntry.contentType;
|
||||
if (aEntry.referrer)
|
||||
shEntry.referrerURI = Services.io.newURI(aEntry.referrer, null, null);
|
||||
|
||||
if (aEntry.cacheKey) {
|
||||
let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
|
||||
cacheKey.data = aEntry.cacheKey;
|
||||
shEntry.cacheKey = cacheKey;
|
||||
}
|
||||
|
||||
if (aEntry.ID) {
|
||||
// get a new unique ID for this frame (since the one from the last
|
||||
// start might already be in use)
|
||||
let id = aIdMap[aEntry.ID] || 0;
|
||||
if (!id) {
|
||||
for (id = Date.now(); id in aIdMap.used; id++);
|
||||
aIdMap[aEntry.ID] = id;
|
||||
aIdMap.used[id] = true;
|
||||
}
|
||||
shEntry.ID = id;
|
||||
}
|
||||
|
||||
if (aEntry.docshellID)
|
||||
shEntry.docshellID = aEntry.docshellID;
|
||||
|
||||
if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
|
||||
shEntry.stateData =
|
||||
Cc["@mozilla.org/docshell/structured-clone-container;1"].
|
||||
createInstance(Ci.nsIStructuredCloneContainer);
|
||||
|
||||
shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);
|
||||
}
|
||||
|
||||
if (aEntry.scroll) {
|
||||
let scrollPos = aEntry.scroll.split(",");
|
||||
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
|
||||
shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
|
||||
}
|
||||
|
||||
let childDocIdents = {};
|
||||
if (aEntry.docIdentifier) {
|
||||
// If we have a serialized document identifier, try to find an SHEntry
|
||||
// which matches that doc identifier and adopt that SHEntry's
|
||||
// BFCacheEntry. If we don't find a match, insert shEntry as the match
|
||||
// for the document identifier.
|
||||
let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
|
||||
if (!matchingEntry) {
|
||||
matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
|
||||
aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
|
||||
} else {
|
||||
shEntry.adoptBFCacheEntry(matchingEntry);
|
||||
childDocIdents = matchingEntry.childDocIdents;
|
||||
}
|
||||
}
|
||||
|
||||
if (aEntry.owner_b64) {
|
||||
let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
let binaryData = atob(aEntry.owner_b64);
|
||||
ownerInput.setData(binaryData, binaryData.length);
|
||||
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream);
|
||||
binaryStream.setInputStream(ownerInput);
|
||||
try { // Catch possible deserialization exceptions
|
||||
shEntry.owner = binaryStream.readObject(true);
|
||||
} catch (ex) { dump(ex); }
|
||||
}
|
||||
|
||||
if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
|
||||
for (let i = 0; i < aEntry.children.length; i++) {
|
||||
if (!aEntry.children[i].url)
|
||||
continue;
|
||||
|
||||
// We're getting sessionrestore.js files with a cycle in the
|
||||
// doc-identifier graph, likely due to bug 698656. (That is, we have
|
||||
// an entry where doc identifier A is an ancestor of doc identifier B,
|
||||
// and another entry where doc identifier B is an ancestor of A.)
|
||||
//
|
||||
// If we were to respect these doc identifiers, we'd create a cycle in
|
||||
// the SHEntries themselves, which causes the docshell to loop forever
|
||||
// when it looks for the root SHEntry.
|
||||
//
|
||||
// So as a hack to fix this, we restrict the scope of a doc identifier
|
||||
// to be a node's siblings and cousins, and pass childDocIdents, not
|
||||
// aDocIdents, to _deserializeHistoryEntry. That is, we say that two
|
||||
// SHEntries with the same doc identifier have the same document iff
|
||||
// they have the same parent or their parents have the same document.
|
||||
|
||||
shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i);
|
||||
}
|
||||
}
|
||||
|
||||
return shEntry;
|
||||
},
|
||||
|
||||
sendHistory: function sendHistory() {
|
||||
// We need to package up the session history and send it to the sessionstore
|
||||
let entries = [];
|
||||
let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
|
||||
for (let i = 0; i < history.count; i++) {
|
||||
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false));
|
||||
entries.push(entry);
|
||||
}
|
||||
let index = history.index + 1;
|
||||
sendAsyncMessage("Content:SessionHistory", { entries: entries, index: index });
|
||||
},
|
||||
|
||||
_serializeHistoryEntry: function _serializeHistoryEntry(aEntry) {
|
||||
let entry = { url: aEntry.URI.spec };
|
||||
|
||||
if (aEntry.title && aEntry.title != entry.url)
|
||||
entry.title = aEntry.title;
|
||||
|
||||
if (!(aEntry instanceof Ci.nsISHEntry))
|
||||
return entry;
|
||||
|
||||
let cacheKey = aEntry.cacheKey;
|
||||
if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0)
|
||||
entry.cacheKey = cacheKey.data;
|
||||
|
||||
entry.ID = aEntry.ID;
|
||||
entry.docshellID = aEntry.docshellID;
|
||||
|
||||
if (aEntry.referrerURI)
|
||||
entry.referrer = aEntry.referrerURI.spec;
|
||||
|
||||
if (aEntry.contentType)
|
||||
entry.contentType = aEntry.contentType;
|
||||
|
||||
let x = {}, y = {};
|
||||
aEntry.getScrollPosition(x, y);
|
||||
if (x.value != 0 || y.value != 0)
|
||||
entry.scroll = x.value + "," + y.value;
|
||||
|
||||
if (aEntry.owner) {
|
||||
try {
|
||||
let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIObjectOutputStream);
|
||||
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
|
||||
pipe.init(false, false, 0, 0xffffffff, null);
|
||||
binaryStream.setOutputStream(pipe.outputStream);
|
||||
binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
|
||||
binaryStream.close();
|
||||
|
||||
// Now we want to read the data from the pipe's input end and encode it.
|
||||
let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
|
||||
scriptableStream.setInputStream(pipe.inputStream);
|
||||
let ownerBytes = scriptableStream.readByteArray(scriptableStream.available());
|
||||
// We can stop doing base64 encoding once our serialization into JSON
|
||||
// is guaranteed to handle all chars in strings, including embedded
|
||||
// nulls.
|
||||
entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
|
||||
} catch (e) { dump(e); }
|
||||
}
|
||||
|
||||
entry.docIdentifier = aEntry.BFCacheEntry.ID;
|
||||
|
||||
if (aEntry.stateData != null) {
|
||||
entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
|
||||
entry.structuredCloneVersion = aEntry.stateData.formatVersion;
|
||||
}
|
||||
|
||||
if (!(aEntry instanceof Ci.nsISHContainer))
|
||||
return entry;
|
||||
|
||||
if (aEntry.childCount > 0) {
|
||||
entry.children = [];
|
||||
for (let i = 0; i < aEntry.childCount; i++) {
|
||||
let child = aEntry.GetChildAt(i);
|
||||
if (child)
|
||||
entry.children.push(this._serializeHistoryEntry(child));
|
||||
else // to maintain the correct frame order, insert a dummy entry
|
||||
entry.children.push({ url: "about:blank" });
|
||||
|
||||
// don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
|
||||
if (/^wyciwyg:\/\//.test(entry.children[i].url)) {
|
||||
delete entry.children;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
};
|
||||
|
||||
WebNavigation.init();
|
||||
|
||||
|
||||
let DOMEvents = {
|
||||
init: function() {
|
||||
addEventListener("DOMContentLoaded", this, false);
|
||||
addEventListener("DOMTitleChanged", this, false);
|
||||
addEventListener("DOMLinkAdded", this, false);
|
||||
addEventListener("DOMWillOpenModalDialog", this, false);
|
||||
addEventListener("DOMModalDialogClosed", this, true);
|
||||
addEventListener("DOMWindowClose", this, false);
|
||||
addEventListener("DOMPopupBlocked", this, false);
|
||||
addEventListener("pageshow", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
let document = content.document;
|
||||
switch (aEvent.type) {
|
||||
case "DOMContentLoaded":
|
||||
if (document.documentURIObject.spec == "about:blank")
|
||||
return;
|
||||
|
||||
sendAsyncMessage("DOMContentLoaded", { });
|
||||
|
||||
// Send the session history now too
|
||||
WebNavigation.sendHistory();
|
||||
break;
|
||||
|
||||
case "pageshow":
|
||||
case "pagehide": {
|
||||
if (aEvent.target.defaultView != content)
|
||||
break;
|
||||
|
||||
let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let json = {
|
||||
contentWindowWidth: content.innerWidth,
|
||||
contentWindowHeight: content.innerHeight,
|
||||
windowId: util.outerWindowID,
|
||||
persisted: aEvent.persisted
|
||||
};
|
||||
|
||||
// Clear onload focus to prevent the VKB to be shown unexpectingly
|
||||
// but only if the location has really changed and not only the
|
||||
// fragment identifier
|
||||
let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) {
|
||||
let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||||
focusManager.clearFocus(content);
|
||||
}
|
||||
|
||||
sendAsyncMessage(aEvent.type, json);
|
||||
break;
|
||||
}
|
||||
|
||||
case "DOMPopupBlocked": {
|
||||
let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let json = {
|
||||
windowId: util.outerWindowID,
|
||||
popupWindowURI: {
|
||||
spec: aEvent.popupWindowURI.spec,
|
||||
charset: aEvent.popupWindowURI.originCharset
|
||||
},
|
||||
popupWindowFeatures: aEvent.popupWindowFeatures,
|
||||
popupWindowName: aEvent.popupWindowName
|
||||
};
|
||||
|
||||
sendAsyncMessage("DOMPopupBlocked", json);
|
||||
break;
|
||||
}
|
||||
|
||||
case "DOMTitleChanged":
|
||||
sendAsyncMessage("DOMTitleChanged", { title: document.title });
|
||||
break;
|
||||
|
||||
case "DOMLinkAdded":
|
||||
let target = aEvent.originalTarget;
|
||||
if (!target.href || target.disabled)
|
||||
return;
|
||||
|
||||
let json = {
|
||||
windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
|
||||
href: target.href,
|
||||
charset: document.characterSet,
|
||||
title: target.title,
|
||||
rel: target.rel,
|
||||
type: target.type
|
||||
};
|
||||
|
||||
// rel=icon can also have a sizes attribute
|
||||
if (target.hasAttribute("sizes"))
|
||||
json.sizes = target.getAttribute("sizes");
|
||||
|
||||
sendAsyncMessage("DOMLinkAdded", json);
|
||||
break;
|
||||
|
||||
case "DOMWillOpenModalDialog":
|
||||
case "DOMModalDialogClosed":
|
||||
case "DOMWindowClose":
|
||||
let retvals = sendSyncMessage(aEvent.type, { });
|
||||
for (let i in retvals) {
|
||||
if (retvals[i].preventDefault) {
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DOMEvents.init();
|
||||
|
||||
let ContentScroll = {
|
||||
_scrollOffset: { x: 0, y: 0 },
|
||||
|
||||
init: function() {
|
||||
addMessageListener("Content:SetCacheViewport", this);
|
||||
addMessageListener("Content:SetWindowSize", this);
|
||||
|
||||
addEventListener("scroll", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
addEventListener("MozScrolledAreaChanged", this, false);
|
||||
},
|
||||
|
||||
getScrollOffset: function(aWindow) {
|
||||
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
let scrollX = {}, scrollY = {};
|
||||
cwu.getScrollXY(false, scrollX, scrollY);
|
||||
return { x: scrollX.value, y: scrollY.value };
|
||||
},
|
||||
|
||||
getScrollOffsetForElement: function(aElement) {
|
||||
if (aElement.parentNode == aElement.ownerDocument)
|
||||
return this.getScrollOffset(aElement.ownerDocument.defaultView);
|
||||
return { x: aElement.scrollLeft, y: aElement.scrollTop };
|
||||
},
|
||||
|
||||
setScrollOffsetForElement: function(aElement, aLeft, aTop) {
|
||||
if (aElement.parentNode == aElement.ownerDocument) {
|
||||
aElement.ownerDocument.defaultView.scrollTo(aLeft, aTop);
|
||||
} else {
|
||||
aElement.scrollLeft = aLeft;
|
||||
aElement.scrollTop = aTop;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "Content:SetCacheViewport": {
|
||||
// Set resolution for root view
|
||||
let rootCwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
if (json.id == 1) {
|
||||
rootCwu.setResolution(json.scale, json.scale);
|
||||
if (!WebProgressListener._firstPaint)
|
||||
break;
|
||||
}
|
||||
|
||||
let displayport = new Rect(json.x, json.y, json.w, json.h);
|
||||
if (displayport.isEmpty())
|
||||
break;
|
||||
|
||||
// Map ID to element
|
||||
let element = null;
|
||||
try {
|
||||
element = rootCwu.findElementWithViewId(json.id);
|
||||
} catch(e) {
|
||||
// This could give NS_ERROR_NOT_AVAILABLE. In that case, the
|
||||
// presshell is not available because the page is reloading.
|
||||
}
|
||||
|
||||
if (!element)
|
||||
break;
|
||||
|
||||
let binding = element.ownerDocument.getBindingParent(element);
|
||||
if (binding instanceof Ci.nsIDOMHTMLInputElement && binding.mozIsTextField(false))
|
||||
break;
|
||||
|
||||
// Set the scroll offset for this element if specified
|
||||
if (json.scrollX >= 0 && json.scrollY >= 0) {
|
||||
this.setScrollOffsetForElement(element, json.scrollX, json.scrollY)
|
||||
if (json.id == 1)
|
||||
this._scrollOffset = this.getScrollOffset(content);
|
||||
}
|
||||
|
||||
// Set displayport. We want to set this after setting the scroll offset, because
|
||||
// it is calculated based on the scroll offset.
|
||||
let scrollOffset = this.getScrollOffsetForElement(element);
|
||||
let x = displayport.x - scrollOffset.x;
|
||||
let y = displayport.y - scrollOffset.y;
|
||||
|
||||
if (json.id == 1) {
|
||||
x = Math.round(x * json.scale) / json.scale;
|
||||
y = Math.round(y * json.scale) / json.scale;
|
||||
}
|
||||
|
||||
let win = element.ownerDocument.defaultView;
|
||||
let winCwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
winCwu.setDisplayPortForElement(x, y, displayport.width, displayport.height, element);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Content:SetWindowSize": {
|
||||
let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
cwu.setCSSViewport(json.width, json.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "pagehide":
|
||||
this._scrollOffset = { x: 0, y: 0 };
|
||||
break;
|
||||
|
||||
case "scroll": {
|
||||
let doc = aEvent.target;
|
||||
if (doc != content.document)
|
||||
break;
|
||||
|
||||
this.sendScroll();
|
||||
break;
|
||||
}
|
||||
|
||||
case "MozScrolledAreaChanged": {
|
||||
let doc = aEvent.originalTarget;
|
||||
if (content != doc.defaultView) // We are only interested in root scroll pane changes
|
||||
return;
|
||||
|
||||
sendAsyncMessage("MozScrolledAreaChanged", {
|
||||
width: aEvent.width,
|
||||
height: aEvent.height,
|
||||
left: aEvent.x + content.scrollX
|
||||
});
|
||||
|
||||
// Send event only after painting to make sure content views in the parent process have
|
||||
// been updated.
|
||||
addEventListener("MozAfterPaint", function afterPaint() {
|
||||
removeEventListener("MozAfterPaint", afterPaint, false);
|
||||
sendAsyncMessage("Content:UpdateDisplayPort");
|
||||
}, false);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sendScroll: function sendScroll() {
|
||||
let scrollOffset = this.getScrollOffset(content);
|
||||
if (this._scrollOffset.x == scrollOffset.x && this._scrollOffset.y == scrollOffset.y)
|
||||
return;
|
||||
|
||||
this._scrollOffset = scrollOffset;
|
||||
sendAsyncMessage("scroll", scrollOffset);
|
||||
}
|
||||
};
|
||||
|
||||
ContentScroll.init();
|
||||
|
||||
let ContentActive = {
|
||||
init: function() {
|
||||
addMessageListener("Content:Activate", this);
|
||||
addMessageListener("Content:Deactivate", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "Content:Deactivate":
|
||||
docShell.isActive = false;
|
||||
let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
if (json.keepviewport)
|
||||
break;
|
||||
cwu.setDisplayPortForElement(0, 0, 0, 0, content.document.documentElement);
|
||||
break;
|
||||
|
||||
case "Content:Activate":
|
||||
docShell.isActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ContentActive.init();
|
||||
|
||||
/**
|
||||
* Helper class for IndexedDB, child part. Listens using
|
||||
* the observer service for events regarding IndexedDB
|
||||
* prompts, and sends messages to the parent to actually
|
||||
* show the prompts.
|
||||
*/
|
||||
let IndexedDB = {
|
||||
_permissionsPrompt: "indexedDB-permissions-prompt",
|
||||
_permissionsResponse: "indexedDB-permissions-response",
|
||||
|
||||
_quotaPrompt: "indexedDB-quota-prompt",
|
||||
_quotaResponse: "indexedDB-quota-response",
|
||||
_quotaCancel: "indexedDB-quota-cancel",
|
||||
|
||||
waitingObservers: [],
|
||||
|
||||
init: function IndexedDBPromptHelper_init() {
|
||||
let os = Services.obs;
|
||||
os.addObserver(this, this._permissionsPrompt, false);
|
||||
os.addObserver(this, this._quotaPrompt, false);
|
||||
os.addObserver(this, this._quotaCancel, false);
|
||||
addMessageListener("IndexedDB:Response", this);
|
||||
},
|
||||
|
||||
observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) {
|
||||
if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) {
|
||||
throw new Error("Unexpected topic!");
|
||||
}
|
||||
|
||||
let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let observer = requestor.getInterface(Ci.nsIObserver);
|
||||
|
||||
let contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
|
||||
let contentDocument = contentWindow.document;
|
||||
|
||||
if (aTopic == this._quotaCancel) {
|
||||
observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote to parent
|
||||
sendAsyncMessage("IndexedDB:Prompt", {
|
||||
topic: aTopic,
|
||||
host: contentDocument.documentURIObject.asciiHost,
|
||||
location: contentDocument.location.toString(),
|
||||
data: aData,
|
||||
observerId: this.addWaitingObserver(observer)
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
let payload = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "IndexedDB:Response":
|
||||
let observer = this.getAndRemoveWaitingObserver(payload.observerId);
|
||||
observer.observe(null, payload.responseTopic, payload.permission);
|
||||
}
|
||||
},
|
||||
|
||||
addWaitingObserver: function(aObserver) {
|
||||
let observerId = 0;
|
||||
while (observerId in this.waitingObservers)
|
||||
observerId++;
|
||||
this.waitingObservers[observerId] = aObserver;
|
||||
return observerId;
|
||||
},
|
||||
|
||||
getAndRemoveWaitingObserver: function(aObserverId) {
|
||||
let observer = this.waitingObservers[aObserverId];
|
||||
delete this.waitingObservers[aObserverId];
|
||||
return observer;
|
||||
}
|
||||
};
|
||||
|
||||
IndexedDB.init();
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="error" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
||||
<content orient="vertical">
|
||||
<xul:hbox class="console-row-internal-box" flex="1">
|
||||
<xul:vbox class="console-row-content" flex="1">
|
||||
<xul:hbox class="console-row-msg" align="start">
|
||||
<xul:label class="label title" xbl:inherits="value=typetext"/>
|
||||
<xul:description class="console-error-msg title" xbl:inherits="xbl:text=msg" flex="1"/>
|
||||
</xul:hbox>
|
||||
|
||||
<xul:hbox class="console-row-file" xbl:inherits="hidden=hideSource">
|
||||
<xul:label class="label title" value="&consoleErrFile.label;"/>
|
||||
<xul:label class="title" xbl:inherits="value=href" crop="right"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:hbox class="lineNumberRow" xbl:inherits="line">
|
||||
<xul:label class="label title" value="&consoleErrLine.label;"/>
|
||||
<xul:label class="label title" xbl:inherits="value=line"/>
|
||||
</xul:hbox>
|
||||
</xul:hbox>
|
||||
|
||||
<xul:vbox class="console-row-code" xbl:inherits="hidden=hideCode">
|
||||
<xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
|
||||
<xul:hbox xbl:inherits="hidden=hideCaret">
|
||||
<xul:label class="monospace console-dots title" xbl:inherits="value=errorDots"/>
|
||||
<xul:label class="monospace console-caret title" xbl:inherits="value=errorCaret"/>
|
||||
<xul:spacer flex="1"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="message" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
||||
<content>
|
||||
<xul:hbox class="console-internal-box" flex="1">
|
||||
<xul:vbox class="console-row-content" flex="1">
|
||||
<xul:vbox class="console-row-msg" flex="1">
|
||||
<xul:description class="console-msg-text title" xbl:inherits="xbl:text=msg"/>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="dialog">
|
||||
<content xbl:inherits="orient, closebutton" flex="1">
|
||||
<children/>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<field name="arguments"/>
|
||||
<field name="parent"/>
|
||||
<property name="_scrollbox" readonly="true" onget="return this.getElementsByTagName('scrollbox')[0];"/>
|
||||
|
||||
<constructor><![CDATA[
|
||||
this._closed = false;
|
||||
if (this.hasAttribute("script")) {
|
||||
try {
|
||||
Services.scriptloader.loadSubScript(this.getAttribute("script"), this);
|
||||
} catch(e) {
|
||||
throw("Dialog : Unable to load script : " + this.getAttribute("script") + "\n");
|
||||
}
|
||||
}
|
||||
window.addEventListener("unload", this, true);
|
||||
|
||||
let scrollbox = this._scrollbox;
|
||||
if (scrollbox) {
|
||||
window.addEventListener("resize", this, true);
|
||||
scrollbox.addEventListener("overflow", this, true);
|
||||
}
|
||||
setTimeout(this.load.bind(this), 0);
|
||||
]]></constructor>
|
||||
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
switch(aEvent.type) {
|
||||
case "unload":
|
||||
if (aEvent.originalTarget == document)
|
||||
this._removeDialog();
|
||||
break;
|
||||
|
||||
case "resize":
|
||||
case "overflow":
|
||||
let scrollbox = this._scrollbox;
|
||||
let style = document.defaultView.getComputedStyle(scrollbox, null);
|
||||
let newHeight = Math.ceil(scrollbox.firstChild.getBoundingClientRect().height) +
|
||||
parseInt(style.marginTop) +
|
||||
parseInt(style.marginBottom);
|
||||
scrollbox.style.minHeight = Math.min(window.innerHeight / 2, newHeight) + "px";
|
||||
break;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="load">
|
||||
<body><![CDATA[
|
||||
if (this.hasAttribute("onload")) {
|
||||
let func = new Function(this.getAttribute("onload"));
|
||||
func.call(this);
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="close">
|
||||
<body><![CDATA[
|
||||
if (this.hasAttribute("onclose")) {
|
||||
let func = new Function(this.getAttribute("onclose"));
|
||||
func.call(this);
|
||||
}
|
||||
this._removeDialog();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_removeDialog">
|
||||
<body><![CDATA[
|
||||
window.removeEventListener("unload", this, true);
|
||||
let scrollbox = this._scrollbox;
|
||||
if (scrollbox) {
|
||||
window.removeEventListener("resize", this, true);
|
||||
scrollbox.removeEventListener("overflow", this, true);
|
||||
}
|
||||
|
||||
this.parentNode.parentNode.removeChild(this.parentNode);
|
||||
this._closed = true;
|
||||
|
||||
// emit DOMModalDialogClosed event
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("DOMModalDialogClosed", true, false);
|
||||
let dispatcher = this.parent || getBrowser();
|
||||
dispatcher.dispatchEvent(event);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="waitForClose">
|
||||
<body><![CDATA[
|
||||
while (!this._closed)
|
||||
Services.tm.currentThread.processNextEvent(true);
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,208 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="download-base" extends="chrome://browser/content/bindings/grid.xml#richgrid-item">
|
||||
<content>
|
||||
<xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox class="richgrid-icon-container">
|
||||
<xul:box class="richgrid-icon-box"><xul:image xbl:inherits="src=iconURI"/></xul:box>
|
||||
<xul:box flex="1" />
|
||||
</xul:hbox>
|
||||
<xul:description xbl:inherits="value=label" crop="end"/>
|
||||
</xul:vbox>
|
||||
<children/>
|
||||
</content>
|
||||
<implementation>
|
||||
<field name="nsIDLMgr">Components.interfaces.nsIDownloadManager</field>
|
||||
|
||||
<property name="paused">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return parseInt(this.getAttribute("state")) == this.nsIDLMgr.DOWNLOAD_PAUSED;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
<property name="openable">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return parseInt(this.getAttribute("state")) == this.nsIDLMgr.DOWNLOAD_FINISHED;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
<property name="inProgress">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
var state = parseInt(this.getAttribute("state"));
|
||||
return state == this.nsIDLMgr.DOWNLOAD_NOTSTARTED ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_QUEUED ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_DOWNLOADING ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_PAUSED ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_SCANNING;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
<property name="removable">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
var state = parseInt(this.getAttribute("state"));
|
||||
return state == this.nsIDLMgr.DOWNLOAD_FINISHED ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_CANCELED ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_BLOCKED_PARENTAL ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_BLOCKED_POLICY ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_DIRTY ||
|
||||
state == this.nsIDLMgr.DOWNLOAD_FAILED;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="download-not-started" extends="#download-base">
|
||||
<content orient="horizontal" align="start">
|
||||
<xul:image validate="always" xbl:inherits="src=iconURL"/>
|
||||
<xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox align="center">
|
||||
<xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
|
||||
<xul:label class="normal" xbl:inherits="value=datetime"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox>
|
||||
<xul:label class="normal" xbl:inherits="value=status"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox class="show-on-select" align="center">
|
||||
<xul:button anonid="showpage-button" label="&downloadShowPage.label;"
|
||||
oncommand="Downloads.showPage(document.getBindingParent(this));"/>
|
||||
<xul:spacer flex="1"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
let referrer = this.hasAttribute("referrer");
|
||||
if (!referrer)
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "showpage-button").setAttribute("disabled", "true");
|
||||
]]>
|
||||
</constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
|
||||
<binding id="download-downloading" extends="#download-base">
|
||||
<content orient="horizontal" align="start">
|
||||
<xul:image validate="always" xbl:inherits="src=iconURL"/>
|
||||
<xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox align="center">
|
||||
<xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
|
||||
<xul:label class="normal" xbl:inherits="value=datetime"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox align="center">
|
||||
<xul:progressmeter anonid="progressmeter" mode="normal" value="0" flex="1" xbl:inherits="value=progress,mode=progressmode"/>
|
||||
<xul:button class="download-pause" label="&downloadPause.label;"
|
||||
oncommand="Downloads.pauseDownload(document.getBindingParent(this));"/>
|
||||
<xul:button class="download-cancel" label="&downloadCancel.label;"
|
||||
oncommand="Downloads.cancelDownload(document.getBindingParent(this));"/>
|
||||
</xul:hbox>
|
||||
<xul:label class="normal" xbl:inherits="value=status" crop="end"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="download-paused" extends="#download-base">
|
||||
<content orient="horizontal" align="start">
|
||||
<xul:image validate="always" xbl:inherits="src=iconURL"/>
|
||||
<xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox align="center">
|
||||
<xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
|
||||
<xul:label class="normal" xbl:inherits="value=datetime"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox align="center">
|
||||
<xul:progressmeter anonid="progressmeter" mode="normal" value="0" flex="1" xbl:inherits="value=progress,mode=progressmode"/>
|
||||
<xul:button class="download-resume" label="&downloadResume.label;"
|
||||
oncommand="Downloads.resumeDownload(document.getBindingParent(this));"/>
|
||||
<xul:button class="download-cancel" label="&downloadCancel.label;"
|
||||
oncommand="Downloads.cancelDownload(document.getBindingParent(this));"/>
|
||||
</xul:hbox>
|
||||
<xul:label class="normal" xbl:inherits="value=status" crop="end"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="download-retry" extends="#download-base">
|
||||
<content orient="horizontal" align="start">
|
||||
<xul:image validate="always" xbl:inherits="src=iconURL"/>
|
||||
<xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox align="center">
|
||||
<xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
|
||||
<xul:label class="normal" xbl:inherits="value=datetime"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox>
|
||||
<xul:label class="normal" xbl:inherits="value=status" crop="end" flex="1"/>
|
||||
<xul:label class="hide-on-select download-retry-failed normal" value="&downloadFailed.label;" xbl:inherits="state" />
|
||||
<xul:button class="show-on-select download-retry" label="&downloadRetry.label;"
|
||||
oncommand="Downloads.retryDownload(document.getBindingParent(this));"/>
|
||||
<xul:button class="show-on-select download-remove" label="&downloadRemove.label;"
|
||||
oncommand="Downloads.removeDownload(document.getBindingParent(this));"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="download-done" extends="#download-base">
|
||||
<content orient="horizontal" align="start">
|
||||
<xul:image validate="always" xbl:inherits="src=iconURL"/>
|
||||
<xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox align="center">
|
||||
<xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
|
||||
<xul:label class="normal" xbl:inherits="value=datetime"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox>
|
||||
<xul:label class="normal" xbl:inherits="value=status"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox class="show-on-select" align="center">
|
||||
<xul:button anonid="showpage-button" label="&downloadShowPage.label;"
|
||||
oncommand="Downloads.showPage(document.getBindingParent(this));"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:button anonid="open-button" label="&downloadOpen2.label;"
|
||||
oncommand="Downloads.openDownload(document.getBindingParent(this));"/>
|
||||
<xul:button anonid="remove-button" label="&downloadRemove.label;"
|
||||
oncommand="Downloads.removeDownload(document.getBindingParent(this));"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
let referrer = this.hasAttribute("referrer");
|
||||
if (!referrer)
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "showpage-button").setAttribute("disabled", "true");
|
||||
let file = Downloads._getLocalFile(this.getAttribute("target"));
|
||||
|
||||
let mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
|
||||
let mimeType;
|
||||
try {
|
||||
mimeType = mimeService.getTypeFromFile(file);
|
||||
}
|
||||
catch(e) {}
|
||||
if (!file.exists() || !mimeType)
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "open-button").setAttribute("disabled", "true");
|
||||
]]>
|
||||
</constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="flyoutpanelBinding" extends="xul:box">
|
||||
<resources>
|
||||
<stylesheet src="chrome://browser/skin/flyoutpanel.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<xul:vbox class="flyoutpanel-wrapper">
|
||||
<xul:hbox class="flyoutpanel-header" align="top">
|
||||
<xul:toolbarbutton class="flyout-close-button"
|
||||
command="cmd_flyout_back"
|
||||
xbl:inherits="command"/>
|
||||
<xul:label class="flyout-header-label" xbl:inherits="value=headertext"/>
|
||||
</xul:hbox>
|
||||
<xul:scrollbox class="flyoutpanel-contents" flex="1" orient="vertical">
|
||||
<children/>
|
||||
</xul:scrollbox>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
window.addEventListener('MozContextUIShow', this);
|
||||
window.addEventListener('MozContextUIDismiss', this);
|
||||
window.addEventListener('MozAppbarDismiss', this);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
window.removeEventListener('MozContextUIShow', this);
|
||||
window.removeEventListener('MozContextUIDismiss', this);
|
||||
window.removeEventListener('MozAppbarDismiss', this);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<property name="isVisible" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.hasAttribute("visible");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="hide">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.removeAttribute("visible");
|
||||
DialogUI.popPopup(this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="show">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.setAttribute("visible", "true");
|
||||
DialogUI.pushPopup(this, this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
switch (aEvent.type) {
|
||||
case 'MozContextUIShow':
|
||||
case 'MozAppbarDismiss':
|
||||
case 'MozContextUIDismiss':
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="anonScrollBox" readonly="true"><![CDATA[
|
||||
// Expose the anyonymous scrollbox so ScrollUtils.getScrollboxFromElement can find it.
|
||||
document.getAnonymousElementByAttribute(this, "class", "flyoutpanel-contents");
|
||||
]]></field>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<!-- Work around for bug 835175 -->
|
||||
<handler event="click">false;</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,556 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="richgrid"
|
||||
extends="chrome://global/content/bindings/general.xml#basecontrol">
|
||||
|
||||
<content>
|
||||
<html:div id="grid-div" anonid="grid" class="richgrid-grid">
|
||||
<children/>
|
||||
</html:div>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMXULSelectControlElement">
|
||||
<property name="_grid" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'grid');"/>
|
||||
<field name="controller">null</field>
|
||||
|
||||
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
||||
|
||||
<method name="clearSelection">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// 'selection' and 'selected' are confusingly overloaded here
|
||||
// as richgrid is adopting multi-select behavior, but select/selected are already being
|
||||
// used to describe triggering the default action of a tile
|
||||
if (this._selectedItem){
|
||||
this._selectedItem.selected = false;
|
||||
this._selectedItem = null;
|
||||
}
|
||||
|
||||
for (let childItem of this.selectedItems) {
|
||||
childItem.selected = false;
|
||||
}
|
||||
// reset context actions
|
||||
this._contextActions = null;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="toggleItemSelection">
|
||||
<parameter name="anItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
anItem.selected = !anItem.selected;
|
||||
this._fireOnSelectionChange();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="selectItem">
|
||||
<parameter name="anItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.clearSelection();
|
||||
this._selectedItem = anItem;
|
||||
this._selectedItem.selected = true;
|
||||
|
||||
this._fireOnSelect();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleItemClick">
|
||||
<parameter name="aItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.controller)
|
||||
this.controller.handleItemClick(aItem);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleItemContextMenu">
|
||||
<parameter name="aItem"/>
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// we'll republish this as a selectionchange event on the grid
|
||||
aEvent.stopPropagation();
|
||||
this.toggleItemSelection(aItem);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_contextActions">null</field>
|
||||
<property name="contextActions">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
// return the subset of verbs that apply to all selected tiles
|
||||
// use cached list, it'll get cleared out when selection changes
|
||||
if (this._contextActions) {
|
||||
return this._contextActions;
|
||||
}
|
||||
let tileNodes = this.selectedItems;
|
||||
if (!tileNodes.length) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
function cloneSet(aSet) {
|
||||
let clone = new Set();
|
||||
if (aSet) {
|
||||
for (let item of aSet) {
|
||||
clone.add(item);
|
||||
}
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
// given one or more sets of values,
|
||||
// return a set with only those values present in each
|
||||
let initialItem = tileNodes[0];
|
||||
|
||||
let verbSet = cloneSet(initialItem.contextActions);
|
||||
for (let i=1; i<tileNodes.length; i++){
|
||||
let set = tileNodes[i].contextActions;
|
||||
for (let item of verbSet) {
|
||||
if (!set.has(item)){
|
||||
verbSet.delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._contextActions = verbSet;
|
||||
// returns Set
|
||||
return this._contextActions;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<!-- nsIDOMXULSelectControlElement -->
|
||||
|
||||
<property name="itemCount" readonly="true" onget="return this.children.length;"/>
|
||||
|
||||
<field name="_selectedItem">null</field>
|
||||
<property name="selectedItem" onget="return this._selectedItem;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this.selectItem(val);
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<!-- partial implementation of multiple selection interface -->
|
||||
<property name="selectedItems">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.querySelectorAll("richgriditem[selected='true']");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<property name="selectedIndex">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.getIndexOfItem(this._selectedItem);
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val >= 0) {
|
||||
let selected = this.getItemAtIndex(val);
|
||||
this.selectItem(selected);
|
||||
} else {
|
||||
this.clearSelection();
|
||||
}
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="appendItem">
|
||||
<parameter name="aLabel"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
this.appendChild(addition);
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="insertItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aLabel"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let existing = this.getItemAtIndex(anIndex);
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
if (existing) {
|
||||
this.insertBefore(addition, existing);
|
||||
} else {
|
||||
this.appendChild(addition);
|
||||
}
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="removeItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let removal = this.getItemAtIndex(anIndex);
|
||||
this.removeChild(removal);
|
||||
this.arrangeItems();
|
||||
return removal;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getIndexOfItem">
|
||||
<parameter name="anItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!anItem)
|
||||
return -1;
|
||||
|
||||
return Array.prototype.indexOf.call(this.children, anItem);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getItemAtIndex">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isIndexInBounds(anIndex))
|
||||
return null;
|
||||
return this.children.item(anIndex);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for offsetting selection and checking bounds -->
|
||||
|
||||
<property name="isSelectionAtStart" readonly="true"
|
||||
onget="return this.selectedIndex == 0;"/>
|
||||
|
||||
<property name="isSelectionAtEnd" readonly="true"
|
||||
onget="return this.selectedIndex == (this.itemCount - 1);"/>
|
||||
|
||||
<property name="isSelectionInStartRow" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.selectedIndex < this.columnCount;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<property name="isSelectionInEndRow" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
let lowerBound = (this.rowCount - 1) * this.columnCount;
|
||||
let higherBound = this.rowCount * this.columnCount;
|
||||
|
||||
return this.selectedIndex >= lowerBound &&
|
||||
this.selectedIndex < higherBound;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="offsetSelection">
|
||||
<parameter name="aOffset"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let newIndex = this.selectedIndex + aOffset;
|
||||
if (this._isIndexInBounds(newIndex))
|
||||
this.selectedIndex = newIndex;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="offsetSelectionByRow">
|
||||
<parameter name="aRowOffset"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let newIndex = this.selectedIndex + (this.columnCount * aRowOffset);
|
||||
if (this._isIndexInBounds(newIndex))
|
||||
this.selectedIndex -= this.columnCount;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for grid layout management -->
|
||||
|
||||
<field name="_rowCount">0</field>
|
||||
<property name="rowCount" readonly="true" onget="return this._rowCount;"/>
|
||||
|
||||
<field name="_columnCount">0</field>
|
||||
<property name="columnCount" readonly="true" onget="return this._columnCount;"/>
|
||||
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
|
||||
<!-- define a height where we consider an item not yet rendered
|
||||
10 is the height of the empty item (padding/border etc. only) -->
|
||||
<field name="_itemHeightRenderThreshold">10</field>
|
||||
|
||||
<method name="arrangeItems">
|
||||
<parameter name="aNumRows"/>
|
||||
<parameter name="aNumColumns"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.itemCount <= 0)
|
||||
return;
|
||||
let item = this.getItemAtIndex(0);
|
||||
if (item == null)
|
||||
return;
|
||||
let gridItemRect = item.getBoundingClientRect();
|
||||
|
||||
// cap the number of times we reschedule calling arrangeItems
|
||||
let maxRetries = 5;
|
||||
|
||||
// delay as necessary until the item has a proper height
|
||||
if (gridItemRect.height <= this._itemHeightRenderThreshold) {
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
// retry of arrangeItems already scheduled
|
||||
return;
|
||||
}
|
||||
|
||||
// track how many times we've attempted arrangeItems
|
||||
this._scheduledArrangeItemsTries++;
|
||||
|
||||
if (maxRetries > this._scheduledArrangeItemsTries) {
|
||||
// schedule re-try of arrangeItems at the next tick
|
||||
this._scheduledArrangeItemsTimerId = setTimeout(this.arrangeItems.bind(this), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// items ready to arrange (or retries max exceeded)
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
clearTimeout(this._scheduledArrangeItemsTimerId);
|
||||
delete this._scheduledArrangeItemsTimerId;
|
||||
}
|
||||
if (this._scheduledArrangeItemsTries) {
|
||||
this._scheduledArrangeItemsTries = 0;
|
||||
}
|
||||
|
||||
// Autocomplete is a binding within a binding, so we have to step
|
||||
// up an additional parentNode.
|
||||
let container = null;
|
||||
if (this.parentNode.id == "results-vbox" ||
|
||||
this.parentNode.id == "searches-vbox")
|
||||
container = this.parentNode.parentNode.getBoundingClientRect();
|
||||
else
|
||||
container = this.parentNode.getBoundingClientRect();
|
||||
|
||||
// If we don't have valid dimensions we can't arrange yet
|
||||
if (!container.height || !gridItemRect.height)
|
||||
return;
|
||||
|
||||
// We favor overflowing horizontally, not vertically
|
||||
let maxRowCount = Math.floor(container.height / gridItemRect.height) - 1;
|
||||
|
||||
if (aNumRows) {
|
||||
this._rowCount = aNumRows;
|
||||
} else {
|
||||
this._rowCount = Math.min(this.itemCount, maxRowCount);
|
||||
}
|
||||
if (aNumColumns) {
|
||||
this._columnCount = aNumColumns;
|
||||
} else {
|
||||
this._columnCount = Math.ceil(this.itemCount / this._rowCount);
|
||||
}
|
||||
|
||||
this._grid.style.width = (this._columnCount * gridItemRect.width) + "px";
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Inteface to suppress selection events -->
|
||||
|
||||
<field name="_suppressOnSelect"/>
|
||||
<property name="suppressOnSelect"
|
||||
onget="return this.getAttribute('suppressonselect') == 'true';"
|
||||
onset="this.setAttribute('suppressonselect', val);"/>
|
||||
|
||||
<!-- Internal methods -->
|
||||
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
if (this.controller && this.controller.gridBoundCallback != undefined)
|
||||
this.controller.gridBoundCallback();
|
||||
|
||||
// XXX This event was never actually implemented (bug 223411).
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("contentgenerated", true, true);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<method name="_isIndexInBounds">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return anIndex >= 0 && anIndex < this.itemCount;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_createItemElement">
|
||||
<parameter name="aLabel"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let item = this.ownerDocument.createElement("richgriditem");
|
||||
item.control = this;
|
||||
item.setAttribute("label", aLabel);
|
||||
if (aValue)
|
||||
item.setAttribute("value", aValue);
|
||||
|
||||
// copy over the richgrid's data-contextactions as each child is created
|
||||
if (this.hasAttribute("data-contextactions")) {
|
||||
item.setAttribute("data-contextactions", this.getAttribute("data-contextactions"));
|
||||
}
|
||||
return item;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_fireOnSelect">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.suppressOnSelect || this._suppressOnSelect)
|
||||
return;
|
||||
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="_fireOnSelectionChange">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// flush out selection-related cached properties so they get recalc'd next time
|
||||
this._contextActions = null;
|
||||
|
||||
// fire an event?
|
||||
if (this.suppressOnSelect || this._suppressOnSelect)
|
||||
return;
|
||||
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("selectionchange", true, true);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="richgrid-item">
|
||||
<content>
|
||||
<xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content">
|
||||
<xul:hbox class="richgrid-icon-container">
|
||||
<xul:box class="richgrid-icon-box"><xul:image xbl:inherits="src=iconURI"/></xul:box>
|
||||
<xul:box flex="1" />
|
||||
</xul:hbox>
|
||||
<xul:description xbl:inherits="value=label" crop="end"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<property name="_box" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-richgrid-item');"/>
|
||||
<property name="color" onset="this._color = val; this.setColor();" onget="return this._color;"/>
|
||||
<property name="selected"
|
||||
onget="return this.getAttribute('selected') == 'true';"
|
||||
onset="this.setAttribute('selected', val);"/>
|
||||
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
// Bindings don't get bound until the item is displayed,
|
||||
// so we have to reset the background color when we get
|
||||
// created.
|
||||
this.setColor();
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<property name="control">
|
||||
<getter><![CDATA[
|
||||
var parent = this.parentNode;
|
||||
while (parent) {
|
||||
if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
|
||||
return parent;
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
return null;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<method name="setColor">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.color != undefined) {
|
||||
this._box.parentNode.setAttribute("customColorPresent", "true");
|
||||
this._box.style.backgroundColor = this.color;
|
||||
} else {
|
||||
this._box.parentNode.removeAttribute("customColorPresent");
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_contextActions">null</field>
|
||||
<property name="contextActions">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if(!this._contextActions) {
|
||||
this._contextActions = new Set();
|
||||
let actionSet = this._contextActions;
|
||||
let actions = this.getAttribute("data-contextactions");
|
||||
if (actions) {
|
||||
actions.split(/[,\s]+/).forEach(function(verb){
|
||||
actionSet.add(verb);
|
||||
});
|
||||
}
|
||||
}
|
||||
return this._contextActions;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="click" button="0">
|
||||
<![CDATA[
|
||||
// left-click/touch handler
|
||||
this.control.handleItemClick(this, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="contextmenu">
|
||||
<![CDATA[
|
||||
// fires for right-click, long-click and (keyboard) contextmenu input
|
||||
// TODO: handle cross-slide event when it becomes available,
|
||||
// .. using contextmenu is a stop-gap measure to allow us to
|
||||
// toggle the selected state of tiles in a grid
|
||||
this.control.handleItemContextMenu(this, event);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="pageaction" display="xul:hbox">
|
||||
<content>
|
||||
<xul:hbox align="center">
|
||||
<xul:image xbl:inherits="src=image" class="pageaction-image"/>
|
||||
</xul:hbox>
|
||||
<xul:vbox pack="center" flex="1">
|
||||
<xul:label class="pageaction-title" xbl:inherits="value=title" crop="end"/>
|
||||
<xul:label class="pageaction-desc" value="" xbl:inherits="value=description" crop="end"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<binding id="selection-binding">
|
||||
<content>
|
||||
<html:div flex="1" class="selection-overlay-inner window-width window-height" anonid="selection-overlay-inner">
|
||||
<xul:stack>
|
||||
<html:div anonid="selection-overlay-debug" class="window-width window-height"/>
|
||||
<xul:toolbarbutton id="selectionhandle-start" label="^" left="0" top="10" hidden="false"/>
|
||||
<xul:toolbarbutton id="selectionhandle-end" label="^" left="100" top="10" hidden="false"/>
|
||||
</xul:stack>
|
||||
</html:div>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<field name="_selectionOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-inner").parentNode;</field>
|
||||
<field name="_selectionDebugOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-debug");</field>
|
||||
|
||||
<property name="enabled">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val) {
|
||||
this._selectionOverlay.setAttribute("class", "selection-overlay window-width window-height");
|
||||
} else {
|
||||
this._selectionOverlay.setAttribute("class", "selection-overlay-hidden");
|
||||
while(this._selectionDebugOverlay.hasChildNodes()) this._selectionDebugOverlay.removeChild(this._selectionDebugOverlay.firstChild);
|
||||
}
|
||||
return val;
|
||||
]]>
|
||||
</setter>
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return (this._selectionOverlay.getAttribute("class") == "selection-overlay");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<property name="displayDebugLayer">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val) {
|
||||
this._selectionDebugOverlay.style.display = "block";
|
||||
this._debugLayerVisible = true;
|
||||
} else {
|
||||
this._selectionDebugOverlay.style.display = "none";
|
||||
this._debugLayerVisible = false;
|
||||
}
|
||||
return this._debugLayerVisible;
|
||||
]]>
|
||||
</setter>
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if (this._debugLayerVisible == "undefined")
|
||||
this._debugLayerVisible = false;
|
||||
return this._debugLayerVisible;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="init">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.enabled = true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="shutdown">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.enabled = false;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="addDebugRect">
|
||||
<parameter name="aLeft"/>
|
||||
<parameter name="aTop"/>
|
||||
<parameter name="aRight"/>
|
||||
<parameter name="aBottom"/>
|
||||
<parameter name="aColor"/>
|
||||
<parameter name="aFill"/>
|
||||
<parameter name="aId"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let e = document.createElement("div");
|
||||
this._selectionDebugOverlay.appendChild(e);
|
||||
e.style.position = "fixed";
|
||||
e.style.left = aLeft +"px";
|
||||
e.style.top = aTop +"px";
|
||||
e.style.right = aRight +"px";
|
||||
e.style.bottom = aBottom +"px";
|
||||
e.style.maxWidth = (aRight - aLeft) +"px";
|
||||
e.style.width = (aRight - aLeft) +"px";
|
||||
e.style.maxHeight = (aBottom - aTop) +"px";
|
||||
e.style.height = (aBottom - aTop) +"px";
|
||||
if (aFill == undefined) {
|
||||
e.style.backgroundColor = aColor;
|
||||
} else {
|
||||
if (aFill) {
|
||||
e.style.backgroundColor = aColor;
|
||||
} else {
|
||||
e.style.border = "2px solid " + aColor;
|
||||
}
|
||||
}
|
||||
e.style.opacity = "0.5";
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,173 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="documenttab">
|
||||
<content observes="bcast_urlbarState">
|
||||
<xul:stack class="documenttab-container">
|
||||
<xul:box anonid="thumbnail" class="documenttab-thumbnail" />
|
||||
<xul:image anonid="favicon" class="documenttab-favicon"
|
||||
observes="bcast_urlbarState" width="26" height="26"/>
|
||||
|
||||
<xul:label anonid="title" class="documenttab-title" bottom="0" start="0" end="0" crop="end"/>
|
||||
<xul:box anonid="selection" class="documenttab-crop"/>
|
||||
<xul:box anonid="selection" class="documenttab-selection"/>
|
||||
<xul:button anonid="close" class="documenttab-close" observes="bcast_urlbarState" end="0" top="0"
|
||||
onclick="event.stopPropagation(); document.getBindingParent(this)._onClose()"/>
|
||||
</xul:stack>
|
||||
</content>
|
||||
|
||||
<handlers>
|
||||
<handler event="click" clickcount="1" action="this._onClick()"/>
|
||||
<handler event="dblclick" action="this._onDoubleClick(); event.stopPropagation();"/>
|
||||
</handlers>
|
||||
|
||||
<implementation>
|
||||
<field name="_thumbnail" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "thumbnail");</field>
|
||||
<field name="_close" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "close");</field>
|
||||
<field name="_title" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "title");</field>
|
||||
<field name="_favicon" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "favicon");</field>
|
||||
<field name="_container" readonly="true">this.parentNode;</field>
|
||||
|
||||
<method name="_onClick">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._container.selectedTab = this;
|
||||
let selectFn = new Function("event", this._container.parentNode.getAttribute("onselect"));
|
||||
selectFn.call(this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_onDoubleClick">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._container.selectedTab = this;
|
||||
let selectFn = new Function("event", this._container.parentNode.getAttribute("ondbltap"));
|
||||
selectFn.call(this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_onClose">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let callbackFunc = this._container.parentNode.getAttribute("onclosetab");
|
||||
let closeFn = new Function("event", callbackFunc);
|
||||
closeFn.call(this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateTitle">
|
||||
<parameter name="title"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._title.value = title;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateFavicon">
|
||||
<parameter name="src"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._favicon.src = src;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateThumbnailSource">
|
||||
<parameter name="browser"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._thumbnail.style.backgroundImage = "-moz-element(#" + browser.id + ")";
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="tablist">
|
||||
<content>
|
||||
<xul:arrowscrollbox anonid="tabs-scrollbox" class="tabs-scrollbox" flex="1" orient="horizontal"
|
||||
clicktoscroll="true" />
|
||||
</content>
|
||||
|
||||
<handlers>
|
||||
<handler event="dblclick" action="this._onDoubleClick();"/>
|
||||
</handlers>
|
||||
|
||||
<implementation>
|
||||
<field name="strip">document.getAnonymousElementByAttribute(this, "anonid", "tabs-scrollbox");</field>
|
||||
<field name="_selectedTab">null</field>
|
||||
|
||||
<!-- Used by the chrome input handler -->
|
||||
<property name="anonScrollBox"
|
||||
readonly="true"
|
||||
onget="return this.strip;"/>
|
||||
|
||||
<property name="selectedTab" onget="return this._selectedTab;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (this._selectedTab)
|
||||
this._selectedTab.removeAttribute("selected");
|
||||
|
||||
if (val)
|
||||
val.setAttribute("selected", "true");
|
||||
|
||||
this._selectedTab = val;
|
||||
this.strip.ensureElementIsVisible(val);
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="addTab">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let tab = document.createElement("documenttab");
|
||||
this.strip.appendChild(tab);
|
||||
return tab;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="removeTab">
|
||||
<parameter name="aTab"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.strip.removeChild(aTab);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_onDoubleTap">
|
||||
<body>
|
||||
<![CDATA[
|
||||
new Function("event", this.getAttribute("ondoubletap")).call();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_onDoubleClick">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// ignore mouse events if we're interacting with touch input
|
||||
if (!InputSourceHelper.isPrecise)
|
||||
return;
|
||||
new Function("event", this.getAttribute("ondoubletap")).call();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % checkboxDTD SYSTEM "chrome://browser/locale/checkbox.dtd">
|
||||
%checkboxDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="checkbox-toggleswitch" display="xul:box" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
|
||||
<content>
|
||||
<xul:hbox>
|
||||
<xul:description anonid="onlabel" class="onlabel" value="&checkbox.yes.label;" xbl:inherits="value=onlabel"/>
|
||||
<xul:description anonid="offlabel" class="offlabel" value="&checkbox.no.label;" xbl:inherits="value=offlabel"/>
|
||||
<xul:radiogroup anonid="group" xbl:inherits="disabled">
|
||||
<xul:radio anonid="on" class="checkbox-radio-on"/>
|
||||
<xul:radio anonid="off" class="checkbox-radio-off"/>
|
||||
</xul:radiogroup>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
<implementation>
|
||||
<constructor><![CDATA[
|
||||
this.setChecked(this.checked);
|
||||
]]></constructor>
|
||||
|
||||
<field name="_group">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "group");
|
||||
</field>
|
||||
|
||||
<field name="_on">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "on");
|
||||
</field>
|
||||
|
||||
<field name="_onlabel">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "onlabel");
|
||||
</field>
|
||||
|
||||
<field name="_off">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "off");
|
||||
</field>
|
||||
|
||||
<field name="_offlabel">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "offlabel");
|
||||
</field>
|
||||
|
||||
<property name="onlabel"
|
||||
onget="return this._onlabel.value"
|
||||
onset="this._offlabel.value=val"/>
|
||||
|
||||
<property name="offlabel"
|
||||
onget="return this._offlabel.value"
|
||||
onset="this._offlabel.value=val"/>
|
||||
|
||||
<method name="setChecked">
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let change = (aValue != this.checked);
|
||||
|
||||
if (aValue) {
|
||||
this.setAttribute("checked", "true");
|
||||
this._group.selectedItem = this._on;
|
||||
} else {
|
||||
this.removeAttribute("checked");
|
||||
this._group.selectedItem = this._off;
|
||||
}
|
||||
|
||||
if (change) {
|
||||
this.fireEvent("CheckboxStateChange");
|
||||
this.fireEvent("command");
|
||||
}
|
||||
|
||||
return aValue;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="fireEvent">
|
||||
<parameter name="aName"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent(aName, true, true);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_onClickOrTap">
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
aEvent.stopPropagation();
|
||||
this.setChecked(!this.checked);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="click" button="0" phase="capturing">
|
||||
<![CDATA[
|
||||
this._onClickOrTap(event);
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="setting-fulltoggle-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.input.classList.add("toggleswitch");
|
||||
]]>
|
||||
</constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="setting-fulltoggle-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-boolint">
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.input.classList.add("toggleswitch");
|
||||
]]>
|
||||
</constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="setting-fulltoggle-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-localized-bool">
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.input.classList.add("toggleswitch");
|
||||
]]>
|
||||
</constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -0,0 +1,364 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Utility singleton for manipulating bookmarks.
|
||||
*/
|
||||
var Bookmarks = {
|
||||
get metroRoot() {
|
||||
return PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0];
|
||||
},
|
||||
|
||||
logging: false,
|
||||
log: function(msg) {
|
||||
if (this.logging) {
|
||||
Services.console.logStringMessage(msg);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* fixupColorFormat - convert a decimal color value to a valid
|
||||
* css color string of the format '#123456'
|
||||
*/
|
||||
fixupColorFormat: function bv_fixupColorFormat(aColor) {
|
||||
let color = aColor.toString(16);
|
||||
while (color.length < 6)
|
||||
color = "0" + color;
|
||||
return "#" + color;
|
||||
},
|
||||
|
||||
getFaveIconPrimaryColor: function bh_getFaveIconPrimaryColor(aBookmarkId) {
|
||||
if (PlacesUtils.annotations.itemHasAnnotation(aBookmarkId, 'metro/faveIconColor'))
|
||||
return PlacesUtils.annotations.getItemAnnotation(aBookmarkId, 'metro/faveIconColor');
|
||||
return "";
|
||||
},
|
||||
|
||||
setFaveIconPrimaryColor: function bh_setFaveIconPrimaryColor(aBookmarkId, aColorStr) {
|
||||
var anno = [{ name: "metro/faveIconColor",
|
||||
type: Ci.nsIAnnotationService.TYPE_STRING,
|
||||
flags: 0,
|
||||
value: aColorStr,
|
||||
expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
|
||||
PlacesUtils.setAnnotationsForItem(aBookmarkId, anno);
|
||||
},
|
||||
|
||||
addForURI: function bh_addForURI(aURI, aTitle, callback) {
|
||||
this.isURIBookmarked(aURI, function (isBookmarked) {
|
||||
if (isBookmarked)
|
||||
return;
|
||||
|
||||
let bookmarkTitle = aTitle || aURI.spec;
|
||||
let bookmarkService = PlacesUtils.bookmarks;
|
||||
let bookmarkId = bookmarkService.insertBookmark(Bookmarks.metroRoot,
|
||||
aURI,
|
||||
bookmarkService.DEFAULT_INDEX,
|
||||
bookmarkTitle);
|
||||
|
||||
// XXX Used for browser-chrome tests
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("BookmarkCreated", true, false);
|
||||
window.dispatchEvent(event);
|
||||
|
||||
if (callback)
|
||||
callback(bookmarkId);
|
||||
});
|
||||
},
|
||||
|
||||
isURIBookmarked: function bh_isURIBookmarked(aURI, callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
PlacesUtils.asyncGetBookmarkIds(aURI, function(aItemIds) {
|
||||
callback(aItemIds && aItemIds.length > 0);
|
||||
}, this);
|
||||
},
|
||||
|
||||
removeForURI: function bh_removeForURI(aURI, callback) {
|
||||
// XXX blargle xpconnect! might not matter, but a method on
|
||||
// nsINavBookmarksService that takes an array of items to
|
||||
// delete would be faster. better yet, a method that takes a URI!
|
||||
PlacesUtils.asyncGetBookmarkIds(aURI, function(aItemIds) {
|
||||
aItemIds.forEach(PlacesUtils.bookmarks.removeItem);
|
||||
if (callback)
|
||||
callback(aURI, aItemIds);
|
||||
|
||||
// XXX Used for browser-chrome tests
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("BookmarkRemoved", true, false);
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
|
||||
* fills it with the user's bookmarks.
|
||||
*
|
||||
* @param aSet Control implementing nsIDOMXULSelectControlElement.
|
||||
* @param {Number} aLimit Maximum number of items to show in the view.
|
||||
* @param aRoot Bookmark root to show in the view.
|
||||
*/
|
||||
function BookmarksView(aSet, aLimit, aRoot) {
|
||||
this._set = aSet;
|
||||
this._set.controller = this;
|
||||
|
||||
this._limit = aLimit;
|
||||
|
||||
this._changes = new BookmarkChangeListener(this);
|
||||
PlacesUtils.bookmarks.addObserver(this._changes, false);
|
||||
|
||||
// This also implicitly calls `getBookmarks`
|
||||
this.root = aRoot;
|
||||
}
|
||||
|
||||
BookmarksView.prototype = {
|
||||
_limit: null,
|
||||
_set: null,
|
||||
_changes: null,
|
||||
_root: null,
|
||||
_sort: 0, // Natural bookmark order.
|
||||
|
||||
get sort() {
|
||||
return this._sort;
|
||||
},
|
||||
|
||||
set sort(aSort) {
|
||||
this._sort = aSort;
|
||||
this.getBookmarks();
|
||||
},
|
||||
|
||||
get root() {
|
||||
return this._root;
|
||||
},
|
||||
|
||||
set root(aRoot) {
|
||||
this._root = aRoot;
|
||||
this.getBookmarks();
|
||||
},
|
||||
|
||||
handleItemClick: function bv_handleItemClick(aItem) {
|
||||
let url = aItem.getAttribute("value");
|
||||
BrowserUI.goToURI(url);
|
||||
},
|
||||
|
||||
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
||||
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']");
|
||||
},
|
||||
|
||||
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
||||
return aItem.getAttribute("bookmarkId");
|
||||
},
|
||||
|
||||
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||||
for (let name in aAttrs)
|
||||
anItem.setAttribute(name, aAttrs[name]);
|
||||
},
|
||||
|
||||
getBookmarks: function bv_getBookmarks() {
|
||||
let options = gHistSvc.getNewQueryOptions();
|
||||
options.queryType = options.QUERY_TYPE_BOOKMARKS;
|
||||
options.excludeQueries = true; // Don't include "smart folders"
|
||||
options.maxResults = this._limit;
|
||||
options.sortingMode = this._sort;
|
||||
|
||||
let query = gHistSvc.getNewQuery();
|
||||
query.setFolders([Bookmarks.metroRoot], 1);
|
||||
|
||||
let result = gHistSvc.executeQuery(query, options);
|
||||
let rootNode = result.root;
|
||||
rootNode.containerOpen = true;
|
||||
let childCount = rootNode.childCount;
|
||||
|
||||
for (let i = 0; i < childCount; i++) {
|
||||
let node = rootNode.getChild(i);
|
||||
|
||||
// Ignore folders, separators, undefined item types, etc.
|
||||
if (node.type != node.RESULT_TYPE_URI &&
|
||||
node.type != node.RESULT_TYPE_VISIT &&
|
||||
node.type != node.RESULT_TYPE_FULL_VISIT)
|
||||
continue;
|
||||
|
||||
this.addBookmark(node.itemId);
|
||||
}
|
||||
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
|
||||
inCurrentView: function bv_inCurrentView(aParentId, aIndex, aItemType) {
|
||||
if (this._root && aParentId != this._root)
|
||||
return false;
|
||||
|
||||
if (this._limit && aIndex >= this._limit)
|
||||
return false;
|
||||
|
||||
if (aItemType != PlacesUtils.bookmarks.TYPE_BOOKMARK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
clearBookmarks: function bv_clearBookmarks() {
|
||||
while (this._set.itemCount > 0)
|
||||
this._set.removeItemAt(0);
|
||||
},
|
||||
|
||||
addBookmark: function bv_addBookmark(aBookmarkId) {
|
||||
let bookmarks = PlacesUtils.bookmarks;
|
||||
|
||||
let index = bookmarks.getItemIndex(aBookmarkId);
|
||||
let uri = bookmarks.getBookmarkURI(aBookmarkId);
|
||||
let title = bookmarks.getItemTitle(aBookmarkId) || uri.spec;
|
||||
let item = this._set.insertItemAt(index, title, uri.spec);
|
||||
item.setAttribute("bookmarkId", aBookmarkId);
|
||||
this._updateFavicon(aBookmarkId, item, uri);
|
||||
},
|
||||
|
||||
_updateFavicon: function _updateFavicon(aBookmarkId, aItem, aUri) {
|
||||
PlacesUtils.favicons.getFaviconURLForPage(aUri, this._gotIcon.bind(this, aBookmarkId, aItem));
|
||||
},
|
||||
|
||||
_gotIcon: function _gotIcon(aBookmarkId, aItem, aIconUri) {
|
||||
aItem.setAttribute("iconURI", aIconUri ? aIconUri.spec : "");
|
||||
|
||||
let color = Bookmarks.getFaveIconPrimaryColor(aBookmarkId);
|
||||
if (color) {
|
||||
aItem.color = color;
|
||||
return;
|
||||
}
|
||||
let url = Services.io.newURI(aIconUri.spec.replace("moz-anno:favicon:",""), "", null)
|
||||
let ca = Components.classes["@mozilla.org/places/colorAnalyzer;1"]
|
||||
.getService(Components.interfaces.mozIColorAnalyzer);
|
||||
ca.findRepresentativeColor(url, function (success, color) {
|
||||
let colorStr = Bookmarks.fixupColorFormat(color);
|
||||
Bookmarks.setFaveIconPrimaryColor(aBookmarkId, colorStr);
|
||||
aItem.color = colorStr;
|
||||
}, this);
|
||||
},
|
||||
|
||||
updateBookmark: function bv_updateBookmark(aBookmarkId) {
|
||||
let item = this._getItemForBookmarkId(aBookmarkId);
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
let bookmarks = PlacesUtils.bookmarks;
|
||||
let oldIndex = this._set.getIndexOfItem(item);
|
||||
let index = bookmarks.getItemIndex(aBookmarkId);
|
||||
|
||||
if (oldIndex != index) {
|
||||
this.removeBookmark(aBookmarkId);
|
||||
this.addBookmark(aBookmarkId);
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = bookmarks.getBookmarkURI(aBookmarkId);
|
||||
let title = bookmarks.getItemTitle(aBookmarkId) || uri.spec;
|
||||
|
||||
item.setAttribute("value", uri.spec);
|
||||
item.setAttribute("label", title);
|
||||
|
||||
this._updateFavicon(aBookmarkId, item, uri);
|
||||
},
|
||||
|
||||
removeBookmark: function bv_removeBookmark(aBookmarkId) {
|
||||
let item = this._getItemForBookmarkId(aBookmarkId);
|
||||
let index = this._set.getIndexOfItem(item);
|
||||
this._set.removeItemAt(index);
|
||||
},
|
||||
|
||||
destruct: function bv_destruct() {
|
||||
PlacesUtils.bookmarks.removeObserver(this._changes);
|
||||
}
|
||||
};
|
||||
|
||||
var BookmarksStartView = {
|
||||
_view: null,
|
||||
get _grid() { return document.getElementById("start-bookmarks-grid"); },
|
||||
|
||||
init: function init() {
|
||||
this._view = new BookmarksView(this._grid, StartUI.maxResultsPerSection, Bookmarks.metroRoot);
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
}
|
||||
};
|
||||
|
||||
var BookmarksPanelView = {
|
||||
_view: null,
|
||||
|
||||
get _grid() { return document.getElementById("bookmarks-list"); },
|
||||
get visible() { return PanelUI.isPaneVisible("bookmarks-container"); },
|
||||
|
||||
init: function init() {
|
||||
this._view = new BookmarksView(this._grid, null, Bookmarks.metroRoot);
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Observes bookmark changes and keeps a linked BookmarksView updated.
|
||||
*
|
||||
* @param aView An instance of BookmarksView.
|
||||
*/
|
||||
function BookmarkChangeListener(aView) {
|
||||
this._view = aView;
|
||||
};
|
||||
|
||||
BookmarkChangeListener.prototype = {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsINavBookmarkObserver
|
||||
onBeginUpdateBatch: function () { },
|
||||
onEndUpdateBatch: function () { },
|
||||
|
||||
onItemAdded: function bCL_onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGUID, aParentGUID) {
|
||||
if (!this._view.inCurrentView(aParentId, aIndex, aItemType))
|
||||
return;
|
||||
|
||||
this._view.addBookmark(aItemId);
|
||||
},
|
||||
|
||||
onItemChanged: function bCL_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue, aLastModified, aItemType, aParentId, aGUID, aParentGUID) {
|
||||
let itemIndex = PlacesUtils.bookmarks.getItemIndex(aItemId);
|
||||
if (!this._view.inCurrentView(aParentId, itemIndex, aItemType))
|
||||
return;
|
||||
|
||||
this._view.updateBookmark(aItemId);
|
||||
},
|
||||
|
||||
onItemMoved: function bCL_onItemMoved(aItemId, aOldParentId, aOldIndex, aNewParentId, aNewIndex, aItemType, aGUID, aOldParentGUID, aNewParentGUID) {
|
||||
let wasInView = this._view.inCurrentView(aOldParentId, aOldIndex, aItemType);
|
||||
let nowInView = this._view.inCurrentView(aNewParentId, aNewIndex, aItemType);
|
||||
|
||||
if (!wasInView && nowInView)
|
||||
this._view.addBookmark(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded);
|
||||
|
||||
if (wasInView && !nowInView)
|
||||
this._view.removeBookmark(aItemId);
|
||||
},
|
||||
|
||||
onBeforeItemRemoved: function (aItemId, aItemType, aParentId, aGUID, aParentGUID) { },
|
||||
onItemRemoved: function bCL_onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
|
||||
if (!this._view.inCurrentView(aParentId, aIndex, aItemType))
|
||||
return;
|
||||
|
||||
this._view.removeBookmark(aItemId);
|
||||
},
|
||||
|
||||
onItemVisited: function(aItemId, aVisitId, aTime, aTransitionType, aURI, aParentId, aGUID, aParentGUID) { },
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsISupports
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
|
||||
};
|
|
@ -0,0 +1,170 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/*
|
||||
* JS modules
|
||||
*/
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
|
||||
"resource://pdf.js/PdfJs.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
|
||||
"resource://gre/modules/DownloadUtils.jsm");
|
||||
|
||||
/*
|
||||
* Services
|
||||
*/
|
||||
|
||||
#ifdef XP_WIN
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "MetroUtils",
|
||||
"@mozilla.org/windows-metroutils;1",
|
||||
"nsIWinMetroUtils");
|
||||
#else
|
||||
// Stub nsIWinMetroUtils implementation for testing on non-Windows platforms:
|
||||
var MetroUtils = {
|
||||
snappedState: Ci.nsIWinMetroUtils.fullScreenLandscape,
|
||||
immersive: false,
|
||||
handPreference: Ci.nsIWinMetroUtils.handPreferenceLeft,
|
||||
unsnap: function() {},
|
||||
launchInDesktop: function() {},
|
||||
pinTileAsync: function() {},
|
||||
unpinTileAsync: function() {},
|
||||
isTilePinned: function() { return false; },
|
||||
keyboardVisible: false,
|
||||
keyboardX: 0,
|
||||
keyboardY: 0,
|
||||
keyboardWidth: 0,
|
||||
keyboardHeight: 0
|
||||
};
|
||||
#endif
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "StyleSheetSvc",
|
||||
"@mozilla.org/content/style-sheet-service;1",
|
||||
"nsIStyleSheetService");
|
||||
XPCOMUtils.defineLazyServiceGetter(window, "gHistSvc",
|
||||
"@mozilla.org/browser/nav-history-service;1",
|
||||
"nsINavHistoryService",
|
||||
"nsIBrowserHistory");
|
||||
XPCOMUtils.defineLazyServiceGetter(window, "gURIFixup",
|
||||
"@mozilla.org/docshell/urifixup;1",
|
||||
"nsIURIFixup");
|
||||
XPCOMUtils.defineLazyServiceGetter(window, "gFaviconService",
|
||||
"@mozilla.org/browser/favicon-service;1",
|
||||
"nsIFaviconService");
|
||||
XPCOMUtils.defineLazyServiceGetter(window, "gFocusManager",
|
||||
"@mozilla.org/focus-manager;1",
|
||||
"nsIFocusManager");
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
||||
"@mozilla.org/xre/app-info;1",
|
||||
"nsICrashReporter");
|
||||
#endif
|
||||
|
||||
/*
|
||||
* window.Rect is used by
|
||||
* http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-Rect
|
||||
* so it is not possible to set a lazy getter for Geometry.jsm.
|
||||
*/
|
||||
Cu.import("resource://gre/modules/Geometry.jsm");
|
||||
|
||||
/*
|
||||
* Browser scripts
|
||||
*/
|
||||
[
|
||||
["WebProgress", "chrome://browser/content/WebProgress.js"],
|
||||
["FindHelperUI", "chrome://browser/content/helperui/FindHelperUI.js"],
|
||||
["FormHelperUI", "chrome://browser/content/helperui/FormHelperUI.js"],
|
||||
["BrowserTouchHandler", "chrome://browser/content/BrowserTouchHandler.js"],
|
||||
["AlertsHelper", "chrome://browser/content/helperui/AlertsHelper.js"],
|
||||
["CapturePickerUI", "chrome://browser/content/helperui/CapturePickerUI.js"],
|
||||
["CharsetMenu", "chrome://browser/content/helperui/CharsetMenu.js"],
|
||||
["AutofillMenuUI", "chrome://browser/content/helperui/MenuUI.js"],
|
||||
["ContextMenuUI", "chrome://browser/content/helperui/MenuUI.js"],
|
||||
["MenuControlUI", "chrome://browser/content/helperui/MenuUI.js"],
|
||||
["MenuPopup", "chrome://browser/content/helperui/MenuUI.js"],
|
||||
["IdentityUI", "chrome://browser/content/helperui/IdentityUI.js"],
|
||||
["IndexedDB", "chrome://browser/content/helperui/IndexedDB.js"],
|
||||
["MasterPasswordUI", "chrome://browser/content/helperui/MasterPasswordUI.js"],
|
||||
["OfflineApps", "chrome://browser/content/helperui/OfflineApps.js"],
|
||||
["SelectHelperUI", "chrome://browser/content/helperui/SelectHelperUI.js"],
|
||||
["SelectionHelperUI", "chrome://browser/content/helperui/SelectionHelperUI.js"],
|
||||
["SharingUI", "chrome://browser/content/helperui/SharingUI.js"],
|
||||
["FullScreenVideo", "chrome://browser/content/video.js"],
|
||||
["AnimatedZoom", "chrome://browser/content/AnimatedZoom.js"],
|
||||
["CommandUpdater", "chrome://browser/content/commandUtil.js"],
|
||||
["ContextCommands", "chrome://browser/content/ContextCommands.js"],
|
||||
["Bookmarks", "chrome://browser/content/bookmarks.js"],
|
||||
["Downloads", "chrome://browser/content/downloads.js"],
|
||||
["BookmarksPanelView", "chrome://browser/content/bookmarks.js"],
|
||||
["ConsolePanelView", "chrome://browser/content/console.js"],
|
||||
["DownloadsPanelView", "chrome://browser/content/downloads.js"],
|
||||
["DownloadsView", "chrome://browser/content/downloads.js"],
|
||||
["Downloads", "chrome://browser/content/downloads.js"],
|
||||
["PreferencesPanelView", "chrome://browser/content/preferences.js"],
|
||||
["BookmarksStartView", "chrome://browser/content/bookmarks.js"],
|
||||
["HistoryView", "chrome://browser/content/history.js"],
|
||||
["HistoryStartView", "chrome://browser/content/history.js"],
|
||||
["HistoryPanelView", "chrome://browser/content/history.js"],
|
||||
["TopSitesView", "chrome://browser/content/TopSites.js"],
|
||||
["TopSitesSnappedView", "chrome://browser/content/TopSites.js"],
|
||||
["TopSitesStartView", "chrome://browser/content/TopSites.js"],
|
||||
["InputSourceHelper", "chrome://browser/content/input.js"],
|
||||
["PageActions", "chrome://browser/content/PageActions.js"],
|
||||
["Sanitizer", "chrome://browser/content/sanitize.js"],
|
||||
["SSLExceptions", "chrome://browser/content/exceptions.js"],
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
["WeaveGlue", "chrome://browser/content/sync.js"],
|
||||
["SyncPairDevice", "chrome://browser/content/sync.js"],
|
||||
["RemoteTabsView", "chrome://browser/content/RemoteTabs.js"],
|
||||
["RemoteTabsPanelView", "chrome://browser/content/RemoteTabs.js"],
|
||||
["RemoteTabsStartView", "chrome://browser/content/RemoteTabs.js"],
|
||||
#endif
|
||||
].forEach(function (aScript) {
|
||||
let [name, script] = aScript;
|
||||
XPCOMUtils.defineLazyGetter(window, name, function() {
|
||||
let sandbox = {};
|
||||
Services.scriptloader.loadSubScript(script, sandbox);
|
||||
return sandbox[name];
|
||||
});
|
||||
});
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
XPCOMUtils.defineLazyGetter(this, "Weave", function() {
|
||||
Components.utils.import("resource://services-sync/main.js");
|
||||
return Weave;
|
||||
});
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Delay load some global scripts using a custom namespace
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(this, "GlobalOverlay", function() {
|
||||
let GlobalOverlay = {};
|
||||
Services.scriptloader.loadSubScript("chrome://global/content/globalOverlay.js", GlobalOverlay);
|
||||
return GlobalOverlay;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
|
||||
let ContentAreaUtils = {};
|
||||
Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
|
||||
return ContentAreaUtils;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "ZoomManager", function() {
|
||||
let sandbox = {};
|
||||
Services.scriptloader.loadSubScript("chrome://global/content/viewZoomOverlay.js", sandbox);
|
||||
return sandbox.ZoomManager;
|
||||
});
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,221 @@
|
|||
/* 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/. */
|
||||
|
||||
browser[remote="false"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/browser.xml#local-browser");
|
||||
}
|
||||
|
||||
browser[remote="true"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/browser.xml#remote-browser");
|
||||
}
|
||||
|
||||
#content-navigator {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#content-navigator");
|
||||
}
|
||||
|
||||
#tabs {
|
||||
-moz-binding: url("chrome://browser/content/bindings/tabs.xml#tablist");
|
||||
}
|
||||
|
||||
documenttab {
|
||||
-moz-binding: url("chrome://browser/content/bindings/tabs.xml#documenttab");
|
||||
}
|
||||
|
||||
appbar {
|
||||
-moz-binding: url('chrome://browser/content/bindings/appbar.xml#appbarBinding');
|
||||
}
|
||||
|
||||
flyoutpanel {
|
||||
-moz-binding: url('chrome://browser/content/bindings/flyoutpanel.xml#flyoutpanelBinding');
|
||||
}
|
||||
|
||||
settings {
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#settings");
|
||||
}
|
||||
|
||||
setting {
|
||||
display: none;
|
||||
}
|
||||
|
||||
setting[type="bool"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#setting-fulltoggle-bool");
|
||||
}
|
||||
|
||||
setting[type="bool"][localized="true"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#setting-fulltoggle-localized-bool");
|
||||
}
|
||||
|
||||
setting[type="boolint"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#setting-fulltoggle-boolint");
|
||||
}
|
||||
|
||||
setting[type="integer"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer");
|
||||
}
|
||||
|
||||
setting[type="control"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control");
|
||||
}
|
||||
|
||||
setting[type="string"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string");
|
||||
}
|
||||
|
||||
setting[type="color"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color");
|
||||
}
|
||||
|
||||
setting[type="file"],
|
||||
setting[type="directory"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path");
|
||||
}
|
||||
|
||||
setting[type="radio"],
|
||||
setting[type="menulist"] {
|
||||
display: -moz-box;
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
|
||||
}
|
||||
|
||||
#selection-overlay {
|
||||
-moz-binding: url("chrome://browser/content/bindings/selectionoverlay.xml#selection-binding");
|
||||
}
|
||||
|
||||
#urlbar-edit {
|
||||
-moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete");
|
||||
}
|
||||
|
||||
#start-autocomplete {
|
||||
-moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete-popup");
|
||||
}
|
||||
|
||||
richgrid {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid");
|
||||
}
|
||||
|
||||
richgriditem {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
|
||||
}
|
||||
|
||||
placeitem {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#place-item");
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
placeitem[type="folder"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#place-folder");
|
||||
}
|
||||
|
||||
placelabel {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#place-label");
|
||||
}
|
||||
|
||||
radio {
|
||||
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
|
||||
}
|
||||
|
||||
checkbox.toggleswitch {
|
||||
-moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#checkbox-toggleswitch");
|
||||
}
|
||||
|
||||
menulist {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#menulist");
|
||||
}
|
||||
|
||||
.chrome-select-option {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#chrome-select-option");
|
||||
}
|
||||
|
||||
/* richlist defaults ------------------------------------------------------- */
|
||||
richlistbox[batch] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#richlistbox-batch");
|
||||
}
|
||||
|
||||
richlistbox[bindingType="contextmenu"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#richlistbox-contextmenu");
|
||||
}
|
||||
|
||||
richlistitem {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#richlistitem");
|
||||
}
|
||||
|
||||
richgriditem[typeName="download"][state="-1"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-not-started");
|
||||
}
|
||||
|
||||
richgriditem[typeName="download"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-downloading");
|
||||
}
|
||||
|
||||
richgriditem[typeName="download"][state="1"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-done");
|
||||
}
|
||||
|
||||
richgriditem[typeName="download"][state="2"],
|
||||
richgriditem[typeName="download"][state="3"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-retry");
|
||||
}
|
||||
|
||||
richgriditem[typeName="download"][state="4"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-paused");
|
||||
}
|
||||
|
||||
richlistitem[type="error"],
|
||||
richlistitem[type="warning"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/console.xml#error");
|
||||
}
|
||||
|
||||
richlistitem[type="message"]{
|
||||
-moz-binding: url("chrome://browser/content/bindings/console.xml#message");
|
||||
}
|
||||
|
||||
dialog {
|
||||
-moz-binding: url("chrome://browser/content/bindings/dialog.xml#dialog");
|
||||
}
|
||||
|
||||
/* Do not allow these to inherit from the toolkit binding */
|
||||
dialog.content-dialog {
|
||||
-moz-binding: none;
|
||||
}
|
||||
|
||||
pageaction {
|
||||
-moz-binding: url("chrome://browser/content/bindings/pageaction.xml#pageaction");
|
||||
}
|
||||
|
||||
arrowbox {
|
||||
-moz-binding: url("chrome://browser/content/bindings/arrowbox.xml#arrowbox");
|
||||
}
|
||||
|
||||
/* Disable context menus in textboxes */
|
||||
.textbox-input-box,
|
||||
.textbox-input-box[spellcheck="true"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#input-box");
|
||||
}
|
||||
|
||||
textbox {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#textbox");
|
||||
}
|
||||
|
||||
textbox[multiline="true"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#textarea");
|
||||
}
|
||||
|
||||
textbox[type="timed"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#timed-textbox");
|
||||
}
|
||||
|
||||
textbox[type="search"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#search-textbox");
|
||||
}
|
||||
|
||||
textbox[type="number"] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/bindings.xml#numberbox");
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,691 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/forms.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % prefsDTD SYSTEM "chrome://browser/locale/preferences.dtd">
|
||||
%prefsDTD;
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/sync.dtd">
|
||||
%syncDTD;
|
||||
#endif
|
||||
]>
|
||||
|
||||
<window id="main-window"
|
||||
onload="Browser.startup();"
|
||||
onunload="Browser.shutdown();"
|
||||
onclose="return Browser.closing();"
|
||||
windowtype="navigator:browser"
|
||||
chromedir="&locale.dir;"
|
||||
title="&brandShortName;"
|
||||
width="1366"
|
||||
height="768"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/browser-scripts.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/browser-ui.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/Util.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/input.js"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/appbar.js"/>
|
||||
|
||||
<broadcasterset id="broadcasterset">
|
||||
<broadcaster id="bcast_contentShowing" disabled="false"/>
|
||||
<broadcaster id="bcast_urlbarState" mode="view"/>
|
||||
<broadcaster id="bcast_preciseInput" input="imprecise"/>
|
||||
<broadcaster id="bcast_windowState" viewstate=""/>
|
||||
</broadcasterset>
|
||||
|
||||
<observerset id="observerset">
|
||||
<observes id="observe_contentShowing" element="bcast_contentShowing" attribute="disabled" onbroadcast="BrowserUI.updateUIFocus();"/>
|
||||
</observerset>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
<!-- basic navigation -->
|
||||
<command id="cmd_back" label="&back.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_forward" label="&forward.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);" observes="bcast_urlbarState"/>
|
||||
<command id="cmd_handleBackspace" oncommand="BrowserUI.handleBackspace();" />
|
||||
<command id="cmd_handleShiftBackspace" oncommand="BrowserUI.handleShiftBackspace();" />
|
||||
<command id="cmd_reload" label="&reload.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_forceReload" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_stop" label="&stop.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_go" label="&go.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_openLocation" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_home" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<!-- tabs -->
|
||||
<command id="cmd_newTab" label="&newtab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_closeTab" label="&closetab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_undoCloseTab" label="&undoclosetab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<command id="cmd_remoteTabs" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
#endif
|
||||
|
||||
<!-- misc -->
|
||||
<command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_quit" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_actions" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_panel" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_flyout_back" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_addBookmark" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_bookmarks" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_history" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_sanitize" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_contextUI" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<!-- screen/display -->
|
||||
<command id="cmd_zoomin" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_zoomout" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_lockscreen" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<command id="cmd_volumeLeft" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_volumeRight" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<!-- scrolling -->
|
||||
<command id="cmd_scrollPageUp" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_scrollPageDown" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<!-- editing -->
|
||||
<command id="cmd_cut" label="&cut.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_copy" label="©.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_copylink" label="©link.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_paste" label="&paste.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_delete" label="&delete.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
<command id="cmd_selectAll" label="&selectAll.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
|
||||
|
||||
<!-- forms navigation -->
|
||||
<command id="cmd_formPrevious" oncommand="FormHelperUI.goToPrevious();"/>
|
||||
<command id="cmd_formNext" oncommand="FormHelperUI.goToNext();"/>
|
||||
<command id="cmd_formClose" oncommand="FormHelperUI.hide();"/>
|
||||
|
||||
<!-- find navigation -->
|
||||
<command id="cmd_findPrevious" oncommand="FindHelperUI.goToPrevious();"/>
|
||||
<command id="cmd_findNext" oncommand="FindHelperUI.goToNext();"/>
|
||||
<command id="cmd_findClose" oncommand="FindHelperUI.hide();"/>
|
||||
<command id="cmd_find" oncommand="FindHelperUI.show();"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<!-- basic navigation -->
|
||||
<key id="key_back" keycode="VK_LEFT" command="cmd_back" modifiers="alt"/>
|
||||
<key id="key_forward" keycode="VK_RIGHT" command="cmd_forward" modifiers="alt"/>
|
||||
<key id="key_backspace" keycode="VK_BACK" command="cmd_handleBackspace"/>
|
||||
<key id="key_shift_backspace" keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
|
||||
<key id="key_reload" keycode="VK_F5" command="cmd_reload"/>
|
||||
<key id="key_reload2" key="r" modifiers="accel" command="cmd_reload"/>
|
||||
<key id="key_forceReload" keycode="VK_F5" modifiers="shift" command="cmd_forceReload"/>
|
||||
<key id="key_forceReload2" key="r" modifiers="accel,shift" command="cmd_forceReload"/>
|
||||
<key id="key_focusURL" key="l" modifiers="accel" command="cmd_openLocation"/>
|
||||
<key id="key_focusURL2" key="&urlbar.accesskey;" modifiers="alt" command="cmd_openLocation"/>
|
||||
<key id="key_home" keycode="VK_HOME" modifiers="accel" command="cmd_home"/>
|
||||
|
||||
<!-- misc -->
|
||||
<key id="key_zoomin" key="+" modifiers="accel" command="cmd_zoomin"/>
|
||||
<key id="key_zoomout" key="-" modifiers="accel" command="cmd_zoomout"/>
|
||||
<key id="key_find" key="f" modifiers="accel" command="cmd_find"/>
|
||||
<key id="key_find" key="/" command="cmd_find"/>
|
||||
<key id="key_findNext" keycode="VK_F3" command="cmd_findNext"/>
|
||||
<key id="key_findNext2" key="g" modifiers="accel" command="cmd_findNext"/>
|
||||
<key id="key_findPrevious" keycode="VK_F3" modifiers="shift" command="cmd_findPrevious"/>
|
||||
<key id="key_findPrevious2" key="g" modifiers="accel,shift" command="cmd_findPrevious"/>
|
||||
<key id="key_quit" key="q" modifiers="accel" command="cmd_quit"/>
|
||||
<key id="key_addBoomkark" key="d" modifiers="accel" command="cmd_addBookmark"/>
|
||||
|
||||
<!-- manage tabs -->
|
||||
<key id="key_newTab" key="t" modifiers="accel" command="cmd_newTab"/>
|
||||
<key id="key_newTab2" key="n" modifiers="accel" command="cmd_newTab"/>
|
||||
<key id="key_closeTab" key="w" modifiers="accel" command="cmd_closeTab"/>
|
||||
<key id="key_closeTab2" keycode="VK_F4" modifiers="accel" command="cmd_closeTab"/>
|
||||
<key id="key_undoCloseTab" key="t" modifiers="accel,shift" command="cmd_undoCloseTab"/>
|
||||
|
||||
<!-- tab selection -->
|
||||
<key id="key_nextTab" oncommand="BrowserUI.selectNextTab();" keycode="VK_TAB" modifiers="accel"/>
|
||||
<key id="key_nextTab2" oncommand="BrowserUI.selectNextTab();" keycode="VK_PAGE_DOWN" modifiers="accel"/>
|
||||
<key id="key_prevTab" oncommand="BrowserUI.selectPreviousTab();" keycode="VK_TAB" modifiers="accel,shift"/>
|
||||
<key id="key_prevTab2" oncommand="BrowserUI.selectPreviousTab();" keycode="VK_PAGE_UP" modifiers="accel"/>
|
||||
<key id="key_selectTab1" oncommand="BrowserUI.selectTabAtIndex(0);" key="1" modifiers="accel"/>
|
||||
<key id="key_selectTab2" oncommand="BrowserUI.selectTabAtIndex(1);" key="2" modifiers="accel"/>
|
||||
<key id="key_selectTab3" oncommand="BrowserUI.selectTabAtIndex(2);" key="3" modifiers="accel"/>
|
||||
<key id="key_selectTab4" oncommand="BrowserUI.selectTabAtIndex(3);" key="4" modifiers="accel"/>
|
||||
<key id="key_selectTab5" oncommand="BrowserUI.selectTabAtIndex(4);" key="5" modifiers="accel"/>
|
||||
<key id="key_selectTab6" oncommand="BrowserUI.selectTabAtIndex(5);" key="6" modifiers="accel"/>
|
||||
<key id="key_selectTab7" oncommand="BrowserUI.selectTabAtIndex(6);" key="7" modifiers="accel"/>
|
||||
<key id="key_selectTab8" oncommand="BrowserUI.selectTabAtIndex(7);" key="8" modifiers="accel"/>
|
||||
<key id="key_selectLastTab" oncommand="BrowserUI.selectTabAtIndex(-1);" key="9" modifiers="accel"/>
|
||||
</keyset>
|
||||
|
||||
<stack id="stack" flex="1">
|
||||
<!-- Page Area -->
|
||||
<vbox id="page">
|
||||
<vbox id="tray" class="tray-toolbar" visible="true" observes="bcast_windowState" >
|
||||
<!-- Tabs -->
|
||||
<hbox id="tabs-container">
|
||||
<box id="tabs" flex="1"
|
||||
observes="bcast_preciseInput"
|
||||
onselect="BrowserUI.selectTabAndDismiss(this);"
|
||||
onclosetab="BrowserUI.closeTab(this);"/>
|
||||
<vbox id="tabs-controls">
|
||||
<toolbarbutton id="newtab-button" command="cmd_newTab" label="&newtab.label;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- Main Toolbar -->
|
||||
<hbox id="toolbar-container" observes="bcast_windowState" >
|
||||
<toolbar id="toolbar" flex="1">
|
||||
<observes element="bcast_windowState" attribute="*"/>
|
||||
<observes element="bcast_urlbarState" attribute="*"/>
|
||||
<hbox id="unified-back-forward-button" class="chromeclass-toolbar-additional"
|
||||
observes="bcast_windowState"
|
||||
context="backForwardMenu" removable="true"
|
||||
forwarddisabled="true"
|
||||
title="Back/Forward">
|
||||
<toolbarbutton id="back-button" class="toolbarbutton"
|
||||
label="Back"
|
||||
command="cmd_back"/>
|
||||
<toolbarbutton id="forward-button" class="toolbarbutton"
|
||||
label="Forward"
|
||||
command="cmd_forward"/>
|
||||
<dummyobservertarget hidden="true"
|
||||
onbroadcast="if (this.getAttribute('disabled') == 'true')
|
||||
this.parentNode.setAttribute('forwarddisabled', 'true');
|
||||
else
|
||||
this.parentNode.removeAttribute('forwarddisabled');">
|
||||
<observes element="cmd_forward" attribute="disabled"/>
|
||||
</dummyobservertarget>
|
||||
</hbox>
|
||||
|
||||
<hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
|
||||
<hbox id="urlbar" flex="1">
|
||||
<box id="identity-box" role="button"
|
||||
onclick="IdentityUI.handleIdentityButtonEvent();"
|
||||
onkeypress="IdentityUI.handleIdentityButtonEvent();">
|
||||
<hbox id="identity-box-inner" align="center" mousethrough="always">
|
||||
<image id="identity-icon"/>
|
||||
<hbox id="identity-icon-labels">
|
||||
<label id="identity-icon-label" class="plain" flex="1"/>
|
||||
<label id="identity-icon-country-label" class="plain"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</box>
|
||||
|
||||
<textbox id="urlbar-edit"
|
||||
type="url"
|
||||
class="uri-element"
|
||||
autocompletesearch="history"
|
||||
autocompletepopup="start-autocomplete"
|
||||
completeselectedindex="true"
|
||||
placeholder="&urlbar.emptytext;"
|
||||
flex="1"
|
||||
ontextentered="BrowserUI.handleUrlbarEnter(param);"
|
||||
onkeydown="BrowserUI.navEditKeyPress();"
|
||||
onclick="BrowserUI._urlbarClicked(event);"
|
||||
onblur="BrowserUI._urlbarBlurred();"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
||||
<hbox id="urlbar-icons" observes="bcast_urlbarState">
|
||||
<toolbarbutton id="tool-reload" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
|
||||
<toolbarbutton id="tool-stop" command="cmd_stop"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<box id="toolbar-transition" observes="bcast_windowState" >
|
||||
<toolbarbutton id="tool-new-tab" command="cmd_newTab"/>
|
||||
</box>
|
||||
</hbox>
|
||||
|
||||
<hbox id="progress-control" layer="true"></hbox>
|
||||
|
||||
<!-- Start UI -->
|
||||
<hbox id="start-container" flex="1" observes="bcast_windowState" class="meta content-height content-width" onclick="false;">
|
||||
<!-- portrait/landscape/filled view -->
|
||||
<hbox id="start" class="start-page" flex="1" observes="bcast_windowState">
|
||||
<scrollbox id="start-scrollbox" orient="horizontal" flex="1">
|
||||
<vbox id="start-topsites" class="meta-section">
|
||||
<label class="meta-section-title" value="&startTopSitesHeader.label;"/>
|
||||
<richgrid id="start-topsites-grid" seltype="single" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-bookmarks" class="meta-section">
|
||||
<label class="meta-section-title" value="&startBookmarksHeader.label;"
|
||||
onclick="PanelUI.show('bookmarks-container');"/>
|
||||
<richgrid id="start-bookmarks-grid" seltype="single" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-history" class="meta-section">
|
||||
<label class="meta-section-title" value="&startHistoryHeader.label;"/>
|
||||
<richgrid id="start-history-grid" seltype="single" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="start-remotetabs" class="meta-section">
|
||||
<label class="meta-section-title" value="&startRemoteTabsHeader.label;"
|
||||
onclick="PanelUI.show('remotetabs-container');"/>
|
||||
<richgrid id="start-remotetabs-grid" seltype="single" flex="1"/>
|
||||
</vbox>
|
||||
</scrollbox>
|
||||
</hbox>
|
||||
<!-- snapped view -->
|
||||
<vbox id="snapped-start" class="start-page" observes="bcast_windowState">
|
||||
<scrollbox id="snapped-scrollbox" orient="vertical" flex="1">
|
||||
<vbox id="snapped-topsites">
|
||||
<label class="meta-section-title" value="&startTopSitesHeader.label;"/>
|
||||
<!-- TODO bug 835999 -->
|
||||
</vbox>
|
||||
<label class="meta-section-title" value="&startBookmarksHeader.label;"
|
||||
onclick="PanelUI.show('bookmarks-container');"/>
|
||||
<label class="meta-section-title" value="&startHistoryHeader.label;"
|
||||
onclick="PanelUI.show('history-container');" inputProcessing="true"/>
|
||||
<label class="meta-section-title" value="&startRemoteTabsHeader.label;"
|
||||
onclick="PanelUI.show('remotetabs-container');" inputProcessing="true"/>
|
||||
</scrollbox>
|
||||
</vbox>
|
||||
<!-- Autocompletion interface -->
|
||||
<box id="start-autocomplete"/>
|
||||
</hbox>
|
||||
</vbox> <!-- end tray -->
|
||||
|
||||
<!-- Content viewport -->
|
||||
<stack id="content-viewport">
|
||||
<deck id="browsers" flex="1"/>
|
||||
<box id="vertical-scroller" class="scroller" orient="vertical" end="0" top="0"/>
|
||||
<box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
|
||||
</stack>
|
||||
</vbox>
|
||||
|
||||
<!-- popup for content navigator helper -->
|
||||
<vbox id="content-navigator" top="0">
|
||||
<textbox id="find-helper-textbox" class="search-bar content-navigator-item" oncommand="FindHelperUI.search(this.value)" oninput="FindHelperUI.updateCommands(this.value);" type="search"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Windows 8 Appbar -->
|
||||
<appbar id="appbar" mousethrough="never" observes="bcast_windowState">
|
||||
<!-- contextual actions temporarily hidden, pending #800996, #831918 -->
|
||||
<hbox id="contextualactions-tray" flex="1" hidden="true">
|
||||
<toolbarbutton id="delete-selected-button" hidden="true" oncommand="Appbar.dispatchContextualAction('delete')"/>
|
||||
<toolbarbutton id="restore-selected-button" hidden="true" oncommand="Appbar.dispatchContextualAction('restore')"/>
|
||||
<toolbarbutton id="pin-selected-button" hidden="true" oncommand="Appbar.dispatchContextualAction('pin')"/>
|
||||
<toolbarbutton id="unpin-selected-button" hidden="true" oncommand="Appbar.dispatchContextualAction('unpin')"/>
|
||||
</hbox>
|
||||
<hbox flex="1">
|
||||
<toolbarbutton id="download-button" oncommand="Appbar.onDownloadButton()"/>
|
||||
<toolbarbutton id="console-button" oncommand="Appbar.onConsoleButton()"/>
|
||||
<toolbarbutton id="jsshell-button" oncommand="Appbar.onJSShellButton()"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<toolbarbutton id="more-button" onclick="Appbar.onMoreButton(event)" />
|
||||
<toolbarbutton id="zoomout-button" oncommand="Appbar.onZoomOutButton()"/>
|
||||
<toolbarbutton id="zoomin-button" oncommand="Appbar.onZoomInButton()"/>
|
||||
<toolbarbutton id="star-button" type="checkbox" oncommand="Appbar.onStarButton()"/>
|
||||
<toolbarbutton id="pin-button" type="checkbox" oncommand="Appbar.onPinButton()"/>
|
||||
</hbox>
|
||||
</appbar>
|
||||
|
||||
<!-- popup for site identity information -->
|
||||
<arrowbox id="identity-container" hidden="true" mode="unknownIdentity" offset="18" flex="1" type="dialog" observes="bcast_urlbarState">
|
||||
<box id="identity-popup-container" flex="1" align="top">
|
||||
<image id="identity-popup-icon"/>
|
||||
<vbox id="identity-popup-content-box" flex="1">
|
||||
<box id="identity-popup-connected-box" flex="1">
|
||||
<label id="identity-popup-connectedToLabel" value="&identity.connectedTo2;"/>
|
||||
<label id="identity-popup-connectedToLabel2" flex="1">&identity.unverifiedsite2;</label>
|
||||
<description id="identity-popup-content-host" flex="1"/>
|
||||
</box>
|
||||
<box id="identity-popup-runBy-box">
|
||||
<label id="identity-popup-runByLabel" value="&identity.runBy2;"/>
|
||||
<description id="identity-popup-content-owner"/>
|
||||
<description id="identity-popup-content-supplemental"/>
|
||||
</box>
|
||||
<description id="identity-popup-content-verifier"/>
|
||||
</vbox>
|
||||
<box id="identity-popup-encryption-box">
|
||||
<image id="identity-popup-encryption-icon"/>
|
||||
<description id="identity-popup-encryption-label"/>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
<hbox id="pageactions-container" hidden="true">
|
||||
<pageaction id="pageaction-findinpage" title="&pageactions.findInPage;"
|
||||
onclick="FindHelperUI.show();"/>
|
||||
<pageaction id="pageaction-share" title="&pageactions.share.page;"
|
||||
onclick="SharingUI.show(getBrowser().currentURI.spec, getBrowser().contentTitle);"/>
|
||||
<pageaction id="pageaction-pinsite" title="&pageactions.pin.site;"
|
||||
onclick="PageActions.pinSite();"/>
|
||||
<pageaction id="pageaction-password" title="&pageactions.password.forget;"
|
||||
onclick="PageActions.forgetPassword(event);"/>
|
||||
<pageaction id="pageaction-reset" title="&pageactions.reset;"
|
||||
onclick="PageActions.clearPagePermissions(event);"/>
|
||||
<pageaction id="pageaction-search" title="&pageactions.search.addNew;"/>
|
||||
<pageaction id="pageaction-charset" title="&pageactions.charEncoding;" onclick="CharsetMenu.show();"/>
|
||||
</hbox>
|
||||
</arrowbox>
|
||||
|
||||
<vbox id="panel-container" hidden="true" class="window-width window-height meta">
|
||||
<hbox id="panel-header">
|
||||
<toolbarbutton id="panel-close-button" command="cmd_panel"/>
|
||||
|
||||
<menulist id="panel-view-switcher" oncommand="PanelUI.switchPane(this.value);">
|
||||
<menupopup>
|
||||
<menuitem label="&bookmarksHeader.label;" value="bookmarks-container" id="menuitem-bookmarks"/>
|
||||
<menuitem label="&startHistoryHeader.label;" value="history-container" id="menuitem-history"/>
|
||||
<menuitem label="&startRemoteTabsHeader.label;" value="remotetabs-container" id="menuitem-remotetabs"/>
|
||||
<menuitem label="&downloadsHeader.label;" value="downloads-container" id="menuitem-downloads"/>
|
||||
<menuitem label="&consoleHeader.label;" value="console-container" id="menuitem-console"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<deck id="panel-items" selectedIndex="0" flex="1">
|
||||
<scrollbox id="bookmarks-container" flex="1">
|
||||
<richgrid id="bookmarks-list" seltype="single" flex="1"/>
|
||||
</scrollbox>
|
||||
<scrollbox id="history-container" flex="1">
|
||||
<richgrid id="history-list" seltype="single" flex="1"/>
|
||||
</scrollbox>
|
||||
<scrollbox id="downloads-container" flex="1">
|
||||
<richgrid id="downloads-list" seltype="single" flex="1"/>
|
||||
</scrollbox>
|
||||
<scrollbox id="remotetabs-container" flex="1">
|
||||
<richgrid id="remotetabs-list" seltype="single" flex="1"/>
|
||||
</scrollbox>
|
||||
<vbox id="console-container" flex="1">
|
||||
<vbox id="console-header" class="panel-list">
|
||||
<label class="panel-header" value="&consoleHeader.label;"/>
|
||||
<hbox align="center">
|
||||
<label value="&consoleCodeEval.label;" control="console-eval-textbox"/>
|
||||
<textbox id="console-eval-textbox" class="toolbar search-bar" value="" onkeypress="ConsolePanelView.onEvalKeyPress(event)" flex="1"/>
|
||||
<button id="console-button-eval" class="show-text" label="&consoleEvaluate.label;" oncommand="ConsolePanelView.evaluateTypein()"/>
|
||||
</hbox>
|
||||
<hbox align="center" pack="end">
|
||||
<radiogroup id="console-filter" oncommand="ConsolePanelView.changeMode();">
|
||||
<radio id="console-filter-all" label="&consoleAll.label;" value="all" selected="true"/>
|
||||
<radio id="console-filter-messages" label="&consoleMessages.label;" value="message"/>
|
||||
<radio id="console-filter-warnings" label="&consoleWarnings.label;" value="warning"/>
|
||||
<radio id="console-filter-errors" label="&consoleErrors.label;" value="error"/>
|
||||
</radiogroup>
|
||||
<button id="console-clear" class="show-text" oncommand="ConsolePanelView.clearConsole();" label="&consoleClear.label;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<richlistbox id="console-box" class="panel-list console-box" flex="1" onkeypress="ConsolePanelView.onConsoleBoxKeyPress(event)" oncontextmenu="ConsolePanelView.onContextMenu(event);"/>
|
||||
</vbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
|
||||
<flyoutpanel id="about-flyoutpanel" headertext="&aboutHeader.label;">
|
||||
<label id="about-product-label" value="&aboutHeaderProduct.label;"/>
|
||||
<label value="&aboutHeaderCompany.label;"/>
|
||||
#expand <label id="about-version-label">__MOZ_APP_VERSION__</label>
|
||||
<label id="about-policy-label"
|
||||
onclick="if (event.button == 0) { Browser.onAboutPolicyClick(); }"
|
||||
class="text-link" value="&aboutHeaderPolicy.label;"/>
|
||||
</flyoutpanel>
|
||||
|
||||
<flyoutpanel id="prefs-flyoutpanel" headertext="&optionsHeader.label;">
|
||||
<settings id="prefs-startup" label="&startup.title;">
|
||||
<setting id="prefs-homepage" title="&homepage.title;" type="menulist" pref="browser.startup.sessionRestore" class="setting-expanded">
|
||||
<menulist id="prefs-homepage-options">
|
||||
<menupopup position="after_end">
|
||||
<menuitem id="prefs-homepage-default" label="&homepage.startPage;" value="false"/>
|
||||
<menuitem id="prefs-homepage-session" label="&homepage.sessionRestore;" value="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</setting>
|
||||
</settings>
|
||||
<setting pref="browser.tabs.tabsOnly" title="&toggleTabsOnly.label;" type="bool"/>
|
||||
<settings id="prefs-sync" label="&sync.title;">
|
||||
<setting id="sync-connect" title="&sync.notconnected;" type="control">
|
||||
<button label="&sync.connect;" oncommand="WeaveGlue.tryConnect();" />
|
||||
</setting>
|
||||
<setting id="sync-connected" class="setting-group" title="&sync.connected;" type="control" collapsed="true">
|
||||
<button id="sync-pairdevice" label="&sync.pair.title;" oncommand="SyncPairDevice.open();" />
|
||||
<button id="sync-details" label="&sync.details;" type="checkbox" autocheck="false" checked="false" oncommand="WeaveGlue.showDetails();" />
|
||||
</setting>
|
||||
<setting id="sync-sync" class="setting-subgroup" type="control" collapsed="true">
|
||||
<button id="sync-syncButton" label="&sync.syncNow;" oncommand="WeaveGlue.sync();"/>
|
||||
</setting>
|
||||
<setting id="sync-device" class="setting-subgroup" type="string" title="&sync.deviceName;" onchange="WeaveGlue.changeName(this);" collapsed="true"/>
|
||||
<setting id="sync-disconnect" class="setting-subgroup" type="control" collapsed="true">
|
||||
<button label="&sync.disconnect;" oncommand="WeaveGlue.disconnect();" />
|
||||
</setting>
|
||||
</settings>
|
||||
<settings id="prefs-privacy" label="&privacy.title;">
|
||||
<setting pref="signon.rememberSignons" title="&rememberPasswords.title;" type="bool"/>
|
||||
<setting pref="privacy.donottrackheader.enabled" title="&doNotTrack.title;" type="bool"/>
|
||||
<setting id="prefs-master-password" title="&masterPassword.title;" type="bool" oncommand="MasterPasswordUI.show(this.value);"/>
|
||||
<setting title="&clearPrivateData2.title;" type="control">
|
||||
<button id="prefs-clear-data" label="&clearPrivateData.button;" command="cmd_sanitize"/>
|
||||
</setting>
|
||||
</settings>
|
||||
</flyoutpanel>
|
||||
|
||||
<!-- Form Helper form validation helper popup -->
|
||||
<arrowbox id="form-helper-validation-container" class="arrowbox-dark" flex="1" hidden="true" offset="0" top="0" left="0">
|
||||
<label/>
|
||||
</arrowbox>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<box id="syncsetup-container" class="perm-modal-block" hidden="true">
|
||||
<dialog id="syncsetup-dialog" class="content-dialog" flex="1">
|
||||
<vbox class="prompt-inner">
|
||||
<hbox class="prompt-title">
|
||||
<description>&sync.setup.title;</description>
|
||||
</hbox>
|
||||
<vbox id="syncsetup-simple" class="syncsetup-page" flex="1">
|
||||
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
|
||||
<description class="syncsetup-desc" flex="1">&sync.setup.pair;</description>
|
||||
<description class="link" flex="1" onclick="WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
|
||||
<separator/>
|
||||
<vbox flex="1" pack="center" align="start">
|
||||
<description id="syncsetup-code1" class="syncsetup-code">....</description>
|
||||
<description id="syncsetup-code2" class="syncsetup-code">....</description>
|
||||
<description id="syncsetup-code3" class="syncsetup-code">....</description>
|
||||
</vbox>
|
||||
<separator/>
|
||||
<description class="link" flex="1" onclick="WeaveGlue.openManual();">&sync.fallback;</description>
|
||||
<separator flex="1"/>
|
||||
</scrollbox>
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" oncommand="WeaveGlue.close();">&sync.setup.cancel;</button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="syncsetup-waiting" class="syncsetup-page" flex="1" hidden="true">
|
||||
<progressmeter id="syncsetup-progressbar" mode="undetermined"/>
|
||||
<vbox id="syncsetup-waiting-top" align="center" flex="1">
|
||||
<description id="syncsetup-waiting-desc" class="syncsetup-desc" flex="1">&sync.setup.waiting2;</description>
|
||||
<description id="syncsetup-waitingdownload-desc" class="syncsetup-desc" hidden="true" flex="1">&sync.setup.waitingdownload;</description>
|
||||
</vbox>
|
||||
<hbox class="prompt-buttons" pack="center" align="end">
|
||||
<button id="syncsetup-waiting-cancel" class="prompt-button" oncommand="WeaveGlue.close();">&sync.setup.cancel;</button>
|
||||
<button id="syncsetup-waiting-close" class="prompt-button" hidden="true" oncommand="WeaveGlue.close();">&sync.setup.close;</button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="syncsetup-fallback" class="syncsetup-page" flex="1" hidden="true">
|
||||
<scrollbox class="prompt-message" orient="vertical" flex="1">
|
||||
<description class="syncsetup-desc" flex="1">&sync.setup.manual;</description>
|
||||
<separator/>
|
||||
<textbox id="syncsetup-account" class="prompt-edit" placeholder="&sync.account;" oninput="WeaveGlue.canConnect();"/>
|
||||
<textbox id="syncsetup-password" class="prompt-edit" placeholder="&sync.password;" type="password" oninput="WeaveGlue.canConnect();"/>
|
||||
<textbox id="syncsetup-synckey" class="prompt-edit" placeholder="&sync.recoveryKey;" oninput="WeaveGlue.canConnect();"/>
|
||||
<separator class="thin"/>
|
||||
<checkbox id="syncsetup-usecustomserver" label="&sync.customServer;" oncommand="WeaveGlue.toggleCustomServer();"/>
|
||||
<textbox id="syncsetup-customserver" class="prompt-edit" placeholder="&sync.serverURL;"/>
|
||||
<separator flex="1"/>
|
||||
</scrollbox>
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" oncommand="WeaveGlue.close();">&sync.setup.cancel;</button>
|
||||
<separator/>
|
||||
<button id="syncsetup-button-connect" class="prompt-button" oncommand="WeaveGlue.close(); WeaveGlue.connect();">&sync.setup.connect;</button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
</box>
|
||||
|
||||
<box id="syncpair-container" class="perm-modal-block" hidden="true">
|
||||
<dialog id="syncpair-dialog" class="content-dialog" flex="1">
|
||||
<vbox class="prompt-inner">
|
||||
<hbox class="prompt-title">
|
||||
<description>&sync.pair.title;</description>
|
||||
</hbox>
|
||||
<vbox id="syncpair-simple" class="syncsetup-page" flex="1">
|
||||
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
|
||||
<description class="syncsetup-desc" flex="1">&sync.pair.description;</description>
|
||||
<description class="link" flex="1" onclick="SyncPairDevice.close(); WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
|
||||
<separator/>
|
||||
<vbox align="center" flex="1">
|
||||
<textbox id="syncpair-code1" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
|
||||
<textbox id="syncpair-code2" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
|
||||
<textbox id="syncpair-code3" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
|
||||
</vbox>
|
||||
<separator flex="1"/>
|
||||
</scrollbox>
|
||||
<hbox class="prompt-buttons" pack="center">
|
||||
<button class="prompt-button" oncommand="SyncPairDevice.close();">&sync.setup.cancel;</button>
|
||||
<button id="syncpair-connectbutton" class="prompt-button" disabled="true" oncommand="SyncPairDevice.connect();">&sync.setup.connect;</button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
</box>
|
||||
#endif
|
||||
|
||||
<box id="context-container" class="menu-container" hidden="true">
|
||||
<vbox id="context-popup" class="menu-popup">
|
||||
<richlistbox id="context-commands" bindingType="contextmenu" flex="1">
|
||||
<!-- Text related -->
|
||||
<richlistitem id="context-copy" type="copy" onclick="ContextCommands.copy();">
|
||||
<label value="©.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-copy-all" type="copy-all" onclick="ContextCommands.copy();">
|
||||
<label value="©All.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-paste" type="paste" onclick="ContextCommands.paste();">
|
||||
<label value="&paste.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-paste-n-go" type="paste-url" onclick="ContextCommands.pasteAndGo();">
|
||||
<label value="&pasteAndGo.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-select-all" type="select-all" onclick="ContextCommands.selectAll();">
|
||||
<label value="&selectAll.label;"/>
|
||||
</richlistitem>
|
||||
|
||||
<!-- Image related -->
|
||||
<richlistitem id="context-viewinnewtab" type="image" onclick="ContextCommands.openInNewTab();">
|
||||
<label value="&contextViewInNewTab.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-saveimage" type="image-loaded" onclick="ContextCommands.saveImage();">
|
||||
<label value="&contextSaveImage.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-saveimage" type="image-loaded" onclick="ContextCommands.saveImageTo();">
|
||||
<label value="&contextSaveImageTo.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-copy-image-location" type="image" onclick="ContextCommands.copyImageLocation();">
|
||||
<label value="&contextCopyImageLocation.label;"/>
|
||||
</richlistitem>
|
||||
|
||||
<!-- Link related -->
|
||||
<richlistitem id="context-openinnewtab" type="link-openable" onclick="ContextCommands.openInNewTab();">
|
||||
<label value="&contextOpenInNewTab.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-bookmark-link" type="link" onclick="ContextCommands.bookmarkLink();">
|
||||
<label value="&contextBookmarkLink.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-copy-link" type="link" onclick="ContextCommands.copyLink();">
|
||||
<label value="&contextCopyLink.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-copy-email" type="mailto" onclick="ContextCommands.copyEmail();">
|
||||
<label value="&contextCopyEmail.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-copy-phone" type="callto" onclick="ContextCommands.copyPhone();">
|
||||
<label value="&contextCopyPhone.label;"/>
|
||||
</richlistitem>
|
||||
|
||||
<!-- Video related -->
|
||||
<richlistitem id="context-play-media" type="media-paused" onclick="ContextCommands.sendCommand('play');">
|
||||
<label value="&contextPlayMedia.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-pause-video" type="media-playing" onclick="ContextCommands.sendCommand('pause');">
|
||||
<label value="&contextPauseMedia.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-videotab" type="video" onclick="ContextCommands.sendCommand('videotab');">
|
||||
<label value="&contextVideoTab.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-save-video" type="video" onclick="ContextCommands.saveVideo();">
|
||||
<label value="&contextSaveVideo.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-save-video" type="video" onclick="ContextCommands.saveVideoTo();">
|
||||
<label value="&contextSaveVideoTo.label;"/>
|
||||
</richlistitem>
|
||||
|
||||
<!-- Misc. related -->
|
||||
<richlistitem id="context-editbookmark" type="edit-bookmark" onclick="ContextCommands.editBookmark();">
|
||||
<label value="&contextEditBookmark.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-removebookmark" type="edit-bookmark" onclick="ContextCommands.removeBookmark();">
|
||||
<label value="&contextRemoveBookmark.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-findinpage" type="find-in-page" onclick="ContextCommands.findInPage();">
|
||||
<label value="&appbarFindInPage.label;"/>
|
||||
</richlistitem>
|
||||
<richlistitem id="context-viewondesktop" type="view-on-desktop" onclick="ContextCommands.viewOnDesktop();">
|
||||
<label value="&appbarViewOnDesktop.label;"/>
|
||||
</richlistitem>
|
||||
</richlistbox>
|
||||
</vbox>
|
||||
</box>
|
||||
|
||||
<vbox id="select-container" class="menu-container" hidden="true">
|
||||
<vbox id="select-popup" class="select-popup">
|
||||
<richlistbox id="select-commands" flex="1"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<box id="menucontrol-container" class="menu-container" hidden="true">
|
||||
<vbox id="menucontrol-popup" class="menu-popup">
|
||||
<richlistbox id="menupopup-commands" onclick="if (event.target != this) MenuControlUI.selectByIndex(this.selectedIndex);" flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
|
||||
<box id="autofill-container" class="menu-container" hidden="true">
|
||||
<vbox id="autofill-popup" class="menu-popup">
|
||||
<richlistbox id="menupopup-commands" onclick="if (event.target != this) AutofillMenuUI.selectByIndex(this.selectedIndex);" flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
|
||||
<!-- alerts for content -->
|
||||
<hbox id="alerts-container" hidden="true" align="start" bottom="0" onclick="AlertsHelper.click(event);">
|
||||
<image id="alerts-image"/>
|
||||
<vbox flex="1">
|
||||
<label id="alerts-title" value=""/>
|
||||
<description id="alerts-text" flex="1"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- Selection overlay - this should be below any content that can have selectable text -->
|
||||
<box class="selection-overlay-hidden" id="selection-overlay"/>
|
||||
</stack>
|
||||
|
||||
<svg:svg height="0">
|
||||
<svg:clipPath id="forward-button-clip-path" clipPathUnits="objectBoundingBox">
|
||||
<svg:path d="M 0,0 C 0.15,0.12 0.25,0.3 0.25,0.5 0.25,0.7 0.15,0.88 0,1 L 1,1 1,0 0,0 z"/>
|
||||
</svg:clipPath>
|
||||
<svg:clipPath id="back-button-clip-path" clipPathUnits="userSpaceOnUse">
|
||||
<svg:path d="m -1,-5 0,4.03 C 3.6,1.8 18,21.4 0,40 l 0,27 10000,0 0,-55 L 0,-5 z" />
|
||||
</svg:clipPath>
|
||||
</svg:svg>
|
||||
|
||||
</window>
|
|
@ -0,0 +1,164 @@
|
|||
/* 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/. */
|
||||
|
||||
|
||||
/**
|
||||
* Command Updater
|
||||
*/
|
||||
var CommandUpdater = {
|
||||
/**
|
||||
* Gets a controller that can handle a particular command.
|
||||
* @param command
|
||||
* A command to locate a controller for, preferring controllers that
|
||||
* show the command as enabled.
|
||||
* @returns In this order of precedence:
|
||||
* - the first controller supporting the specified command
|
||||
* associated with the focused element that advertises the
|
||||
* command as ENABLED
|
||||
* - the first controller supporting the specified command
|
||||
* associated with the global window that advertises the
|
||||
* command as ENABLED
|
||||
* - the first controller supporting the specified command
|
||||
* associated with the focused element
|
||||
* - the first controller supporting the specified command
|
||||
* associated with the global window
|
||||
*/
|
||||
_getControllerForCommand: function(command) {
|
||||
try {
|
||||
var controller = top.document.commandDispatcher.getControllerForCommand(command);
|
||||
if (controller && controller.isCommandEnabled(command))
|
||||
return controller;
|
||||
}
|
||||
catch(e) {
|
||||
}
|
||||
var controllerCount = window.controllers.getControllerCount();
|
||||
for (var i = 0; i < controllerCount; ++i) {
|
||||
var current = window.controllers.getControllerAt(i);
|
||||
try {
|
||||
if (current.supportsCommand(command) && current.isCommandEnabled(command))
|
||||
return current;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
return controller || window.controllers.getControllerForCommand(command);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the state of a XUL <command> element for the specified command
|
||||
* depending on its state.
|
||||
* @param command
|
||||
* The name of the command to update the XUL <command> element for
|
||||
*/
|
||||
updateCommand: function(command) {
|
||||
var enabled = false;
|
||||
try {
|
||||
var controller = this._getControllerForCommand(command);
|
||||
if (controller) {
|
||||
enabled = controller.isCommandEnabled(command);
|
||||
}
|
||||
}
|
||||
catch(ex) { }
|
||||
|
||||
this.enableCommand(command, enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the state of a XUL <command> element for the specified command
|
||||
* depending on its state.
|
||||
* @param command
|
||||
* The name of the command to update the XUL <command> element for
|
||||
*/
|
||||
updateCommands: function(_commands) {
|
||||
var commands = _commands.split(",");
|
||||
for (var command in commands) {
|
||||
this.updateCommand(commands[command]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables or disables a XUL <command> element.
|
||||
* @param command
|
||||
* The name of the command to enable or disable
|
||||
* @param enabled
|
||||
* true if the command should be enabled, false otherwise.
|
||||
*/
|
||||
enableCommand: function(command, enabled) {
|
||||
var element = document.getElementById(command);
|
||||
if (!element)
|
||||
return;
|
||||
if (enabled)
|
||||
element.removeAttribute("disabled");
|
||||
else
|
||||
element.setAttribute("disabled", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs the action associated with a specified command using the most
|
||||
* relevant controller.
|
||||
* @param command
|
||||
* The command to perform.
|
||||
*/
|
||||
doCommand: function(command) {
|
||||
var controller = this._getControllerForCommand(command);
|
||||
if (!controller)
|
||||
return;
|
||||
controller.doCommand(command);
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the label attribute for the specified command.
|
||||
* @param command
|
||||
* The command to update.
|
||||
* @param labelAttribute
|
||||
* The label value to use.
|
||||
*/
|
||||
setMenuValue: function(command, labelAttribute) {
|
||||
var commandNode = top.document.getElementById(command);
|
||||
if (commandNode)
|
||||
{
|
||||
var label = commandNode.getAttribute(labelAttribute);
|
||||
if ( label )
|
||||
commandNode.setAttribute('label', label);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the accesskey attribute for the specified command.
|
||||
* @param command
|
||||
* The command to update.
|
||||
* @param valueAttribute
|
||||
* The value attribute to use.
|
||||
*/
|
||||
setAccessKey: function(command, valueAttribute) {
|
||||
var commandNode = top.document.getElementById(command);
|
||||
if (commandNode)
|
||||
{
|
||||
var value = commandNode.getAttribute(valueAttribute);
|
||||
if ( value )
|
||||
commandNode.setAttribute('accesskey', value);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Inform all the controllers attached to a node that an event has occurred
|
||||
* (e.g. the tree controllers need to be informed of blur events so that they can change some of the
|
||||
* menu items back to their default values)
|
||||
* @param node
|
||||
* The node receiving the event
|
||||
* @param event
|
||||
* The event.
|
||||
*/
|
||||
onEvent: function(node, event) {
|
||||
var numControllers = node.controllers.getControllerCount();
|
||||
var controller;
|
||||
|
||||
for ( var controllerIndex = 0; controllerIndex < numControllers; controllerIndex++ )
|
||||
{
|
||||
controller = node.controllers.getControllerAt(controllerIndex);
|
||||
if ( controller )
|
||||
controller.onEvent(event);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,391 @@
|
|||
/* 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/. */
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var ViewConfig = {
|
||||
get _container() {
|
||||
delete this._container;
|
||||
return this._container = document.getElementById("prefs-container");
|
||||
},
|
||||
|
||||
get _editor() {
|
||||
delete this._editor;
|
||||
return this._editor = document.getElementById("editor");
|
||||
},
|
||||
|
||||
init: function init() {
|
||||
window.addEventListener("resize", this, false);
|
||||
window.addEventListener("prefchange", this, false);
|
||||
window.addEventListener("prefnew", this, false);
|
||||
|
||||
this._handleWindowResize();
|
||||
this.filter("");
|
||||
|
||||
document.getElementById("textbox").focus();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
window.removeEventListener("resize", this, false);
|
||||
window.removeEventListener("prefchange", this, false);
|
||||
window.removeEventListener("prefnew", this, false);
|
||||
},
|
||||
|
||||
filter: function filter(aValue) {
|
||||
let row = document.getElementById("editor-row");
|
||||
row.setAttribute("hidden", aValue != "");
|
||||
|
||||
let container = this._container;
|
||||
container.scrollBoxObject.scrollTo(0, 0);
|
||||
// Clear the list by replacing with a shallow copy
|
||||
let empty = container.cloneNode(false);
|
||||
empty.appendChild(row);
|
||||
container.parentNode.replaceChild(empty, container);
|
||||
this._container = empty;
|
||||
|
||||
let result = Utils.getPrefs(aValue);
|
||||
this._container.setItems(result.map(this._createItem, this));
|
||||
},
|
||||
|
||||
open: function open(aType) {
|
||||
let buttons = document.getElementById("editor-buttons-add");
|
||||
buttons.setAttribute("hidden", "true");
|
||||
|
||||
let shouldFocus = false;
|
||||
let setting = document.getElementById("editor-setting");
|
||||
switch (aType) {
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
setting.setAttribute("type", "integer");
|
||||
setting.setAttribute("min", -Infinity);
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
setting.setAttribute("type", "bool");
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
setting.setAttribute("type", "string");
|
||||
break;
|
||||
}
|
||||
|
||||
setting.removeAttribute("title");
|
||||
setting.removeAttribute("pref");
|
||||
if (setting.input)
|
||||
setting.input.value = "";
|
||||
|
||||
document.getElementById("editor-container").appendChild(this._editor);
|
||||
let nameField = document.getElementById("editor-name");
|
||||
nameField.value = "";
|
||||
|
||||
this._editor.setAttribute("hidden", "false");
|
||||
this._currentItem = null;
|
||||
nameField.focus();
|
||||
},
|
||||
|
||||
close: function close(aValid) {
|
||||
this._editor.setAttribute("hidden", "true");
|
||||
let buttons = document.getElementById("editor-buttons-add");
|
||||
buttons.setAttribute("hidden", "false");
|
||||
|
||||
if (aValid) {
|
||||
let name = document.getElementById("editor-name").inputField.value;
|
||||
if (name != "") {
|
||||
let setting = document.getElementById("editor-setting");
|
||||
setting.setAttribute("pref", name);
|
||||
setting.valueToPreference();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("editor-container").appendChild(this._editor);
|
||||
},
|
||||
|
||||
_currentItem: null,
|
||||
|
||||
delayEdit: function(aItem) {
|
||||
setTimeout(this.edit.bind(this), 0, aItem);
|
||||
},
|
||||
|
||||
edit: function(aItem) {
|
||||
if (!aItem)
|
||||
return;
|
||||
|
||||
let pref = Utils.getPref(aItem.getAttribute("name"));
|
||||
if (pref.lock || !pref.name || aItem == this._currentItem)
|
||||
return;
|
||||
|
||||
this.close(false);
|
||||
this._currentItem = aItem;
|
||||
|
||||
let setting = document.getElementById("editor-setting");
|
||||
let shouldFocus = false;
|
||||
switch (pref.type) {
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
setting.setAttribute("type", "bool");
|
||||
break;
|
||||
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
setting.setAttribute("type", "integer");
|
||||
setting.setAttribute("increment", this.getIncrementForValue(pref.value));
|
||||
setting.setAttribute("min", -Infinity);
|
||||
shouldFocus = true;
|
||||
break;
|
||||
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
setting.setAttribute("type", "string");
|
||||
shouldFocus = true;
|
||||
break;
|
||||
}
|
||||
|
||||
setting.setAttribute("title", pref.name);
|
||||
setting.setAttribute("pref", pref.name);
|
||||
|
||||
this._container.insertBefore(this._editor, aItem);
|
||||
|
||||
let resetButton = document.getElementById("editor-reset");
|
||||
resetButton.setAttribute("disabled", pref.default);
|
||||
|
||||
this._editor.setAttribute("default", pref.default);
|
||||
this._editor.setAttribute("hidden", "false");
|
||||
|
||||
if (shouldFocus && setting.input)
|
||||
setting.input.focus();
|
||||
},
|
||||
|
||||
reset: function reset(aItem) {
|
||||
let setting = document.getElementById("editor-setting");
|
||||
let pref = Utils.getPref(setting.getAttribute("pref"));
|
||||
if (!pref.default)
|
||||
Utils.resetPref(pref.name);
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "resize":
|
||||
this._handleWindowResize();
|
||||
break;
|
||||
|
||||
case "prefchange":
|
||||
case "prefnew":
|
||||
this._handlePrefChange(aEvent.detail, aEvent.type == "prefnew");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_handleWindowResize: function _handleWindowResize() {
|
||||
let mainBox = document.getElementById("main-container");
|
||||
let textbox = document.getElementById("textbox");
|
||||
let height = window.innerHeight - textbox.getBoundingClientRect().height;
|
||||
|
||||
mainBox.setAttribute("height", height);
|
||||
},
|
||||
|
||||
_handlePrefChange: function _handlePrefChange(aIndex, aNew) {
|
||||
let isEditing = !this._editor.hidden;
|
||||
let shouldUpdateEditor = false;
|
||||
if (isEditing) {
|
||||
let setting = document.getElementById("editor-setting");
|
||||
let editorIndex = Utils.getPrefIndex(setting.getAttribute("pref"));
|
||||
shouldUpdateEditor = (aIndex == editorIndex);
|
||||
if(shouldUpdateEditor || aIndex > editorIndex)
|
||||
aIndex += 1;
|
||||
}
|
||||
|
||||
// XXX An item display value will probably fail if a pref is changed in the
|
||||
// background while there is a filter on the pref
|
||||
let item = shouldUpdateEditor ? this._editor.nextSibling
|
||||
: this._container.childNodes[aIndex + 1];// add 1 because of the new pref row
|
||||
if (!item) // the pref is not viewable
|
||||
return;
|
||||
|
||||
if (aNew) {
|
||||
let pref = Utils.getPrefByIndex(aIndex);
|
||||
let row = this._createItem(pref);
|
||||
this._container.insertBefore(row, item);
|
||||
return;
|
||||
}
|
||||
|
||||
let pref = Utils.getPref(item.getAttribute("name"));
|
||||
if (shouldUpdateEditor) {
|
||||
this._editor.setAttribute("default", pref.default);
|
||||
|
||||
let resetButton = document.getElementById("editor-reset");
|
||||
resetButton.disabled = pref.default;
|
||||
}
|
||||
|
||||
item.setAttribute("default", pref.default);
|
||||
item.lastChild.setAttribute("value", pref.value);
|
||||
},
|
||||
|
||||
_createItem: function _createItem(aPref) {
|
||||
let row = document.createElement("richlistitem");
|
||||
|
||||
row.setAttribute("name", aPref.name);
|
||||
row.setAttribute("type", aPref.type);
|
||||
row.setAttribute("role", "button");
|
||||
row.setAttribute("default", aPref.default);
|
||||
|
||||
let label = document.createElement("label");
|
||||
label.setAttribute("class", "preferences-title");
|
||||
label.setAttribute("value", aPref.name);
|
||||
label.setAttribute("crop", "end");
|
||||
row.appendChild(label);
|
||||
|
||||
label = document.createElement("label");
|
||||
label.setAttribute("class", "preferences-value");
|
||||
label.setAttribute("value", aPref.value);
|
||||
label.setAttribute("crop", "end");
|
||||
row.appendChild(label);
|
||||
|
||||
return row;
|
||||
},
|
||||
|
||||
getIncrementForValue: function getIncrementForValue(aValue) {
|
||||
let count = 1;
|
||||
while (aValue >= 100) {
|
||||
aValue /= 10;
|
||||
count *= 10;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
var Utils = {
|
||||
QueryInterface: function(aIID) {
|
||||
if (!aIID.equals(Ci.nsIObserver) && !aIID.equals(Ci.nsISupportsWeakReference))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
},
|
||||
|
||||
get _branch() {
|
||||
delete this._branch;
|
||||
this._branch = Services.prefs.getBranch(null);
|
||||
this._branch.addObserver("", this, true);
|
||||
return this._branch;
|
||||
},
|
||||
|
||||
get _preferences() {
|
||||
delete this._preferences;
|
||||
let list = this._branch.getChildList("", {}).filter(function(element) {
|
||||
return !(/^capability\./.test(element));
|
||||
});
|
||||
return this._preferences = list.sort().map(this.getPref, this);
|
||||
},
|
||||
|
||||
getPrefs: function getPrefs(aValue) {
|
||||
let result = this._preferences.slice();;
|
||||
if (aValue != "") {
|
||||
let reg = this._generateRegexp(aValue);
|
||||
if (!reg)
|
||||
return [];
|
||||
|
||||
result = this._preferences.filter(function(element, index, array) {
|
||||
return reg.test(element.name + ";" + element.value);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getPref: function getPref(aPrefName) {
|
||||
let branch = this._branch;
|
||||
let pref = {
|
||||
name: aPrefName,
|
||||
value: "",
|
||||
default: !branch.prefHasUserValue(aPrefName),
|
||||
lock: branch.prefIsLocked(aPrefName),
|
||||
type: branch.getPrefType(aPrefName)
|
||||
};
|
||||
|
||||
try {
|
||||
switch (pref.type) {
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
pref.value = branch.getBoolPref(aPrefName).toString();
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
pref.value = branch.getIntPref(aPrefName).toString();
|
||||
break;
|
||||
default:
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
pref.value = branch.getComplexValue(aPrefName, Ci.nsISupportsString).data;
|
||||
// Try in case it's a localized string (will throw an exception if not)
|
||||
if (pref.default && /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.value))
|
||||
pref.value = branch.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
|
||||
break;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return pref;
|
||||
},
|
||||
|
||||
getPrefByIndex: function getPrefByIndex(aIndex) {
|
||||
return this._preferences[aIndex];
|
||||
},
|
||||
|
||||
getPrefIndex: function getPrefIndex(aPrefName) {
|
||||
let prefs = this._preferences;
|
||||
let high = prefs.length - 1;
|
||||
let low = 0, middle, element;
|
||||
|
||||
while (low <= high) {
|
||||
middle = parseInt((low + high) / 2);
|
||||
element = prefs[middle];
|
||||
|
||||
if (element.name > aPrefName)
|
||||
high = middle - 1;
|
||||
else if (element.name < aPrefName)
|
||||
low = middle + 1;
|
||||
else
|
||||
return middle;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
|
||||
resetPref: function resetPref(aPrefName) {
|
||||
this._branch.clearUserPref(aPrefName);
|
||||
},
|
||||
|
||||
observe: function observe(aSubject, aTopic, aPrefName) {
|
||||
if (aTopic != "nsPref:changed" || /^capability\./.test(aPrefName)) // avoid displaying "private" preferences
|
||||
return;
|
||||
|
||||
let type = "prefchange";
|
||||
let index = this.getPrefIndex(aPrefName);
|
||||
if (index != - 1) {
|
||||
// update the inner array
|
||||
let pref = this.getPref(aPrefName);
|
||||
this._preferences[index].value = pref.value;
|
||||
}
|
||||
else {
|
||||
// XXX we could do better here
|
||||
let list = this._branch.getChildList("", {}).filter(function(element, index, array) {
|
||||
return !(/^capability\./.test(element));
|
||||
});
|
||||
this._preferences = list.sort().map(this.getPref, this);
|
||||
|
||||
type = "prefnew";
|
||||
index = this.getPrefIndex(aPrefName);
|
||||
}
|
||||
|
||||
let evt = document.createEvent("UIEvents");
|
||||
evt.initUIEvent(type, true, true, window, index);
|
||||
window.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_generateRegexp: function _generateRegexp(aValue) {
|
||||
if (aValue.charAt(0) == "/") {
|
||||
try {
|
||||
let rv = aValue.match(/^\/(.*)\/(i?)$/);
|
||||
return RegExp(rv[1], rv[2]);
|
||||
}
|
||||
catch (e) {
|
||||
return null; // Do nothing on incomplete or bad RegExp
|
||||
}
|
||||
}
|
||||
|
||||
return RegExp(aValue.replace(/([^* \w])/g, "\\$1").replace(/^\*+/, "")
|
||||
.replace(/\*+/g, ".*"), "i");
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/config.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd">
|
||||
%configDTD;
|
||||
]>
|
||||
|
||||
<window id="about:config"
|
||||
onload="ViewConfig.init();"
|
||||
onunload="ViewConfig.uninit();"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/x-javascript" src="chrome://browser/content/config.js"/>
|
||||
|
||||
<vbox class="panel-dark" flex="1">
|
||||
<textbox id="textbox"
|
||||
oncommand="ViewConfig.filter(this.value)"
|
||||
type="search"
|
||||
timeout="400"
|
||||
emptytext="&empty.label;"/>
|
||||
|
||||
<hbox id="main-container" class="panel-dark">
|
||||
<richlistbox id="prefs-container" flex="1" onselect="ViewConfig.delayEdit(this.selectedItem)" batch="25">
|
||||
<richlistitem id="editor-row">
|
||||
<vbox id="editor-container" flex="1">
|
||||
|
||||
<hbox align="center" flex="1">
|
||||
<label value="&newpref.label;" flex="1"/>
|
||||
<spacer flex="1" />
|
||||
<hbox id="editor-buttons-add">
|
||||
<button label="&integer.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_INT)"/>
|
||||
<button label="&boolean.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_BOOL)"/>
|
||||
<button label="&string.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_STRING)"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
||||
<vbox id="editor" hidden="true">
|
||||
<hbox align="center">
|
||||
<textbox id="editor-name" emptytext="&addpref.name;" flex="1"/>
|
||||
<setting id="editor-setting" emptytext="&addpref.value;" onlabel="true" offlabel="false" flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="editor-buttons">
|
||||
<button id="editor-cancel" label="&cancel.label;" oncommand="ViewConfig.close(false)"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="editor-reset" label="&reset.label;" oncommand="ViewConfig.reset(this.parentNode.parentNode.nextSibling)"/>
|
||||
<button id="editor-done" label="&done.label;" oncommand="ViewConfig.close(true)"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
</vbox>
|
||||
</richlistitem>
|
||||
</richlistbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</window>
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
let ConsolePanelView = {
|
||||
_list: null,
|
||||
_inited: false,
|
||||
_evalTextbox: null,
|
||||
_evalFrame: null,
|
||||
_evalCode: "",
|
||||
_bundle: null,
|
||||
_showChromeErrors: -1,
|
||||
_enabledPref: "devtools.errorconsole.enabled",
|
||||
|
||||
init: function cv_init() {
|
||||
if (this._list)
|
||||
return;
|
||||
|
||||
this._list = document.getElementById("console-box");
|
||||
this._evalTextbox = document.getElementById("console-eval-textbox");
|
||||
this._bundle = Strings.browser;
|
||||
|
||||
this._count = 0;
|
||||
this.limit = 250;
|
||||
|
||||
try {
|
||||
// update users using the legacy pref
|
||||
if (Services.prefs.getBoolPref("browser.console.showInPanel")) {
|
||||
Services.prefs.setBoolPref(this._enabledPref, true);
|
||||
Services.prefs.clearUserPref("browser.console.showInPanel");
|
||||
}
|
||||
} catch(ex) {
|
||||
// likely don't have an old pref
|
||||
}
|
||||
this.updateVisibility();
|
||||
Services.prefs.addObserver(this._enabledPref, this, false);
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
if (this._inited)
|
||||
return;
|
||||
this._inited = true;
|
||||
|
||||
this.init(); // In case the panel is selected before init has been called.
|
||||
|
||||
Services.console.registerListener(this);
|
||||
|
||||
this.appendInitialItems();
|
||||
|
||||
// Delay creation of the iframe for startup performance
|
||||
this._evalFrame = document.createElement("iframe");
|
||||
this._evalFrame.id = "console-evaluator";
|
||||
this._evalFrame.collapsed = true;
|
||||
document.getElementById("console-container").appendChild(this._evalFrame);
|
||||
|
||||
this._evalFrame.addEventListener("load", this.loadOrDisplayResult.bind(this), true);
|
||||
},
|
||||
|
||||
uninit: function cv_uninit() {
|
||||
if (this._inited)
|
||||
Services.console.unregisterListener(this);
|
||||
|
||||
Services.prefs.removeObserver(this._enabledPref, this, false);
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return Services.prefs.getBoolPref(this._enabledPref);
|
||||
},
|
||||
|
||||
updateVisibility: function ec_updateVisibility(aVal, aPref) {
|
||||
let button = document.getElementById("menuitem-console");
|
||||
button.hidden = !this.enabled;
|
||||
Appbar._updateDebugButtons();
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "nsPref:changed")
|
||||
this.updateVisibility();
|
||||
else
|
||||
this.appendItem(aSubject);
|
||||
},
|
||||
|
||||
showChromeErrors: function() {
|
||||
if (this._showChromeErrors != -1)
|
||||
return this._showChromeErrors;
|
||||
|
||||
try {
|
||||
let pref = Services.prefs;
|
||||
return this._showChromeErrors = pref.getBoolPref("javascript.options.showInConsole");
|
||||
}
|
||||
catch(ex) {
|
||||
return this._showChromeErrors = false;
|
||||
}
|
||||
},
|
||||
|
||||
appendItem: function cv_appendItem(aObject) {
|
||||
try {
|
||||
// Try to QI it to a script error to get more info
|
||||
let scriptError = aObject.QueryInterface(Ci.nsIScriptError);
|
||||
|
||||
// filter chrome urls
|
||||
if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
|
||||
return;
|
||||
this.appendError(scriptError);
|
||||
}
|
||||
catch (ex) {
|
||||
try {
|
||||
// Try to QI it to a console message
|
||||
let msg = aObject.QueryInterface(Ci.nsIConsoleMessage);
|
||||
|
||||
if (msg.message)
|
||||
this.appendMessage(msg.message);
|
||||
else // observed a null/"clear" message
|
||||
this.clearConsole();
|
||||
}
|
||||
catch (ex2) {
|
||||
// Give up and append the object itself as a string
|
||||
this.appendMessage(aObject);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
appendError: function cv_appendError(aObject) {
|
||||
let row = this.createConsoleRow();
|
||||
let nsIScriptError = Ci.nsIScriptError;
|
||||
|
||||
// Is this error actually just a non-fatal warning?
|
||||
let warning = aObject.flags & nsIScriptError.warningFlag != 0;
|
||||
|
||||
let typetext = warning ? "typeWarning" : "typeError";
|
||||
row.setAttribute("typetext", this._bundle.GetStringFromName(typetext));
|
||||
row.setAttribute("type", warning ? "warning" : "error");
|
||||
row.setAttribute("msg", aObject.errorMessage);
|
||||
row.setAttribute("category", aObject.category);
|
||||
if (aObject.lineNumber || aObject.sourceName) {
|
||||
row.setAttribute("href", aObject.sourceName);
|
||||
row.setAttribute("line", aObject.lineNumber);
|
||||
}
|
||||
else {
|
||||
row.setAttribute("hideSource", "true");
|
||||
}
|
||||
if (aObject.sourceLine) {
|
||||
row.setAttribute("code", aObject.sourceLine.replace(/\s/g, " "));
|
||||
if (aObject.columnNumber) {
|
||||
row.setAttribute("col", aObject.columnNumber);
|
||||
row.setAttribute("errorDots", this.repeatChar(" ", aObject.columnNumber));
|
||||
row.setAttribute("errorCaret", " ");
|
||||
}
|
||||
else {
|
||||
row.setAttribute("hideCaret", "true");
|
||||
}
|
||||
}
|
||||
else {
|
||||
row.setAttribute("hideCode", "true");
|
||||
}
|
||||
|
||||
let mode = document.getElementById("console-filter").value;
|
||||
if (mode != "all" && mode != row.getAttribute("type"))
|
||||
row.collapsed = true;
|
||||
|
||||
this.appendConsoleRow(row);
|
||||
},
|
||||
|
||||
appendMessage: function cv_appendMessage (aMessage) {
|
||||
let row = this.createConsoleRow();
|
||||
row.setAttribute("type", "message");
|
||||
row.setAttribute("msg", aMessage);
|
||||
|
||||
let mode = document.getElementById("console-filter").value;
|
||||
if (mode != "all" && mode != "message")
|
||||
row.collapsed = true;
|
||||
|
||||
this.appendConsoleRow(row);
|
||||
},
|
||||
|
||||
createConsoleRow: function cv_createConsoleRow() {
|
||||
let row = document.createElement("richlistitem");
|
||||
row.setAttribute("class", "console-row");
|
||||
return row;
|
||||
},
|
||||
|
||||
appendConsoleRow: function cv_appendConsoleRow(aRow) {
|
||||
this._list.appendChild(aRow);
|
||||
if (++this._count > this.limit)
|
||||
this.deleteFirst();
|
||||
},
|
||||
|
||||
deleteFirst: function cv_deleteFirst() {
|
||||
let node = this._list.firstChild;
|
||||
this._list.removeChild(node);
|
||||
--this._count;
|
||||
},
|
||||
|
||||
appendInitialItems: function cv_appendInitialItems() {
|
||||
let messages = Services.console.getMessageArray();
|
||||
|
||||
// In case getMessageArray returns 0-length array as null
|
||||
if (!messages)
|
||||
messages = [];
|
||||
|
||||
let limit = messages.length - this.limit;
|
||||
if (limit < 0)
|
||||
limit = 0;
|
||||
|
||||
// Checks if console ever been cleared
|
||||
for (var i = messages.length - 1; i >= limit; --i)
|
||||
if (!messages[i].message)
|
||||
break;
|
||||
|
||||
// Populate with messages after latest "clear"
|
||||
while (++i < messages.length)
|
||||
this.appendItem(messages[i]);
|
||||
},
|
||||
|
||||
clearConsole: function cv_clearConsole() {
|
||||
if (this._count == 0) // already clear
|
||||
return;
|
||||
this._count = 0;
|
||||
|
||||
let newRows = this._list.cloneNode(false);
|
||||
this._list.parentNode.replaceChild(newRows, this._list);
|
||||
this._list = newRows;
|
||||
this.selectedItem = null;
|
||||
},
|
||||
|
||||
changeMode: function cv_changeMode() {
|
||||
let mode = document.getElementById("console-filter").value;
|
||||
if (this._list.getAttribute("mode") != mode) {
|
||||
let rows = this._list.childNodes;
|
||||
for (let i=0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
if (mode == "all" || row.getAttribute ("type") == mode)
|
||||
row.collapsed = false;
|
||||
else
|
||||
row.collapsed = true;
|
||||
}
|
||||
this._list.mode = mode;
|
||||
this._list.scrollToIndex(0);
|
||||
}
|
||||
},
|
||||
|
||||
onContextMenu: function cv_onContextMenu(aEvent) {
|
||||
let row = aEvent.target;
|
||||
let text = ["msg", "href", "line", "code", "col"].map(function(attr) row.getAttribute(attr))
|
||||
.filter(function(x) x).join("\n");
|
||||
|
||||
ContextMenuUI.showContextMenu({
|
||||
target: row,
|
||||
json: {
|
||||
types: ["copy"],
|
||||
string: text
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onEvalKeyPress: function cv_onEvalKeyPress(aEvent) {
|
||||
if (aEvent.keyCode == 13)
|
||||
this.evaluateTypein();
|
||||
},
|
||||
|
||||
onConsoleBoxKeyPress: function cv_onConsoleBoxKeyPress(aEvent) {
|
||||
if ((aEvent.charCode == 99 || aEvent.charCode == 67) && aEvent.ctrlKey && this._list && this._list.selectedItem) {
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
clipboard.copyString(this._list.selectedItem.getAttribute("msg"), document);
|
||||
}
|
||||
},
|
||||
|
||||
evaluateTypein: function cv_evaluateTypein() {
|
||||
this._evalCode = this._evalTextbox.value;
|
||||
this.loadOrDisplayResult();
|
||||
},
|
||||
|
||||
loadOrDisplayResult: function cv_loadOrDisplayResult() {
|
||||
if (this._evalCode) {
|
||||
this._evalFrame.contentWindow.location = "javascript: " + this._evalCode.replace(/%/g, "%25");
|
||||
this._evalCode = "";
|
||||
return;
|
||||
}
|
||||
|
||||
let resultRange = this._evalFrame.contentDocument.createRange();
|
||||
resultRange.selectNode(this._evalFrame.contentDocument.documentElement);
|
||||
let result = resultRange.toString();
|
||||
if (result)
|
||||
Services.console.logStringMessage(result);
|
||||
// or could use appendMessage which doesn't persist
|
||||
},
|
||||
|
||||
repeatChar: function cv_repeatChar(aChar, aCol) {
|
||||
if (--aCol <= 0)
|
||||
return "";
|
||||
|
||||
for (let i = 2; i < aCol; i += i)
|
||||
aChar += aChar;
|
||||
|
||||
return aChar + aChar.slice(0, aCol - aChar.length);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,121 @@
|
|||
/* 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/. */
|
||||
|
||||
dump("### ConsoleAPIObserver.js loaded\n");
|
||||
|
||||
/*
|
||||
* ConsoleAPIObserver
|
||||
*
|
||||
*/
|
||||
|
||||
var ConsoleAPIObserver = {
|
||||
init: function init() {
|
||||
addMessageListener("Browser:TabOpen", this);
|
||||
addMessageListener("Browser:TabClose", this);
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "Browser:TabOpen":
|
||||
Services.obs.addObserver(this, "console-api-log-event", false);
|
||||
break;
|
||||
case "Browser:TabClose":
|
||||
Services.obs.removeObserver(this, "console-api-log-event", false);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function observe(aMessage, aTopic, aData) {
|
||||
let contentWindowId = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
aMessage = aMessage.wrappedJSObject;
|
||||
if (aMessage.ID != contentWindowId)
|
||||
return;
|
||||
|
||||
let mappedArguments = Array.map(aMessage.arguments, this.formatResult, this);
|
||||
let joinedArguments = Array.join(mappedArguments, " ");
|
||||
|
||||
if (aMessage.level == "error" || aMessage.level == "warn") {
|
||||
let flag = (aMessage.level == "error" ? Ci.nsIScriptError.errorFlag : Ci.nsIScriptError.warningFlag);
|
||||
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
|
||||
consoleMsg.init(joinedArguments, null, null, 0, 0, flag, "content javascript");
|
||||
Services.console.logMessage(consoleMsg);
|
||||
} else if (aMessage.level == "trace") {
|
||||
let bundle = Services.strings.createBundle("chrome://global/locale/headsUpDisplay.properties");
|
||||
let args = aMessage.arguments;
|
||||
let filename = this.abbreviateSourceURL(args[0].filename);
|
||||
let functionName = args[0].functionName || bundle.GetStringFromName("stacktrace.anonymousFunction");
|
||||
let lineNumber = args[0].lineNumber;
|
||||
|
||||
let body = bundle.formatStringFromName("stacktrace.outputMessage", [filename, functionName, lineNumber], 3);
|
||||
body += "\n";
|
||||
args.forEach(function(aFrame) {
|
||||
body += aFrame.filename + " :: " + aFrame.functionName + " :: " + aFrame.lineNumber + "\n";
|
||||
});
|
||||
|
||||
Services.console.logStringMessage(body);
|
||||
} else {
|
||||
Services.console.logStringMessage(joinedArguments);
|
||||
}
|
||||
},
|
||||
|
||||
getResultType: function getResultType(aResult) {
|
||||
let type = aResult === null ? "null" : typeof aResult;
|
||||
if (type == "object" && aResult.constructor && aResult.constructor.name)
|
||||
type = aResult.constructor.name;
|
||||
return type.toLowerCase();
|
||||
},
|
||||
|
||||
formatResult: function formatResult(aResult) {
|
||||
let output = "";
|
||||
let type = this.getResultType(aResult);
|
||||
switch (type) {
|
||||
case "string":
|
||||
case "boolean":
|
||||
case "date":
|
||||
case "error":
|
||||
case "number":
|
||||
case "regexp":
|
||||
output = aResult.toString();
|
||||
break;
|
||||
case "null":
|
||||
case "undefined":
|
||||
output = type;
|
||||
break;
|
||||
default:
|
||||
if (aResult.toSource) {
|
||||
try {
|
||||
output = aResult.toSource();
|
||||
} catch (ex) { }
|
||||
}
|
||||
if (!output || output == "({})") {
|
||||
output = aResult.toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
abbreviateSourceURL: function abbreviateSourceURL(aSourceURL) {
|
||||
// Remove any query parameters.
|
||||
let hookIndex = aSourceURL.indexOf("?");
|
||||
if (hookIndex > -1)
|
||||
aSourceURL = aSourceURL.substring(0, hookIndex);
|
||||
|
||||
// Remove a trailing "/".
|
||||
if (aSourceURL[aSourceURL.length - 1] == "/")
|
||||
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
|
||||
|
||||
// Remove all but the last path component.
|
||||
let slashIndex = aSourceURL.lastIndexOf("/");
|
||||
if (slashIndex > -1)
|
||||
aSourceURL = aSourceURL.substring(slashIndex + 1);
|
||||
|
||||
return aSourceURL;
|
||||
}
|
||||
};
|
||||
|
||||
ConsoleAPIObserver.init();
|
||||
|
|
@ -0,0 +1,696 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This stays here because otherwise it's hard to tell if there's a parsing error
|
||||
dump("### Content.js loaded\n");
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
let Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Services", function() {
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
return Services;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Rect", function() {
|
||||
Cu.import("resource://gre/modules/Geometry.jsm");
|
||||
return Rect;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Point", function() {
|
||||
Cu.import("resource://gre/modules/Geometry.jsm");
|
||||
return Point;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gFocusManager",
|
||||
"@mozilla.org/focus-manager;1", "nsIFocusManager");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gDOMUtils",
|
||||
"@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
|
||||
|
||||
let XULDocument = Ci.nsIDOMXULDocument;
|
||||
let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
|
||||
let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
|
||||
let HTMLFrameElement = Ci.nsIDOMHTMLFrameElement;
|
||||
let HTMLFrameSetElement = Ci.nsIDOMHTMLFrameSetElement;
|
||||
let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
|
||||
let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
|
||||
|
||||
const kReferenceDpi = 240; // standard "pixel" size used in some preferences
|
||||
|
||||
const kStateActive = 0x00000001; // :active pseudoclass for elements
|
||||
|
||||
/*
|
||||
* ElementTouchHelper
|
||||
*
|
||||
* Assists users by watching for mouse clicks in content and redirect
|
||||
* them to the best found target.
|
||||
*/
|
||||
const ElementTouchHelper = {
|
||||
get radius() {
|
||||
let prefs = Services.prefs;
|
||||
delete this.radius;
|
||||
return this.radius = { "top": prefs.getIntPref("ui.touch.radius.topmm"),
|
||||
"right": prefs.getIntPref("ui.touch.radius.rightmm"),
|
||||
"bottom": prefs.getIntPref("ui.touch.radius.bottommm"),
|
||||
"left": prefs.getIntPref("ui.touch.radius.leftmm")
|
||||
};
|
||||
},
|
||||
|
||||
get weight() {
|
||||
delete this.weight;
|
||||
return this.weight = { "visited": Services.prefs.getIntPref("ui.touch.radius.visitedWeight")
|
||||
};
|
||||
},
|
||||
|
||||
/* Retrieve the closest element to a point by looking at borders position */
|
||||
getClosest: function getClosest(aWindowUtils, aX, aY) {
|
||||
if (!this.dpiRatio)
|
||||
this.dpiRatio = aWindowUtils.displayDPI / kReferenceDpi;
|
||||
|
||||
let dpiRatio = this.dpiRatio;
|
||||
|
||||
let target = aWindowUtils.elementFromPoint(aX, aY,
|
||||
true, /* ignore root scroll frame*/
|
||||
false); /* don't flush layout */
|
||||
|
||||
// return early if the click is just over a clickable element
|
||||
if (this._isElementClickable(target))
|
||||
return target;
|
||||
|
||||
let nodes = aWindowUtils.nodesFromRect(aX, aY, this.radius.top * dpiRatio,
|
||||
this.radius.right * dpiRatio,
|
||||
this.radius.bottom * dpiRatio,
|
||||
this.radius.left * dpiRatio, true, false);
|
||||
|
||||
let threshold = Number.POSITIVE_INFINITY;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let current = nodes[i];
|
||||
if (!current.mozMatchesSelector || !this._isElementClickable(current))
|
||||
continue;
|
||||
|
||||
let rect = current.getBoundingClientRect();
|
||||
let distance = this._computeDistanceFromRect(aX, aY, rect);
|
||||
|
||||
// increase a little bit the weight for already visited items
|
||||
if (current && current.mozMatchesSelector("*:visited"))
|
||||
distance *= (this.weight.visited / 100);
|
||||
|
||||
if (distance < threshold) {
|
||||
target = current;
|
||||
threshold = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
},
|
||||
|
||||
_isElementClickable: function _isElementClickable(aElement) {
|
||||
const selector = "a,:link,:visited,[role=button],button,input,select,textarea,label";
|
||||
for (let elem = aElement; elem; elem = elem.parentNode) {
|
||||
if (this._hasMouseListener(elem))
|
||||
return true;
|
||||
if (elem.mozMatchesSelector && elem.mozMatchesSelector(selector))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_computeDistanceFromRect: function _computeDistanceFromRect(aX, aY, aRect) {
|
||||
let x = 0, y = 0;
|
||||
let xmost = aRect.left + aRect.width;
|
||||
let ymost = aRect.top + aRect.height;
|
||||
|
||||
// compute horizontal distance from left/right border depending if X is
|
||||
// before/inside/after the element's rectangle
|
||||
if (aRect.left < aX && aX < xmost)
|
||||
x = Math.min(xmost - aX, aX - aRect.left);
|
||||
else if (aX < aRect.left)
|
||||
x = aRect.left - aX;
|
||||
else if (aX > xmost)
|
||||
x = aX - xmost;
|
||||
|
||||
// compute vertical distance from top/bottom border depending if Y is
|
||||
// above/inside/below the element's rectangle
|
||||
if (aRect.top < aY && aY < ymost)
|
||||
y = Math.min(ymost - aY, aY - aRect.top);
|
||||
else if (aY < aRect.top)
|
||||
y = aRect.top - aY;
|
||||
if (aY > ymost)
|
||||
y = aY - ymost;
|
||||
|
||||
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
},
|
||||
|
||||
_els: Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService),
|
||||
_clickableEvents: ["mousedown", "mouseup", "click"],
|
||||
_hasMouseListener: function _hasMouseListener(aElement) {
|
||||
let els = this._els;
|
||||
let listeners = els.getListenerInfoFor(aElement, {});
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (this._clickableEvents.indexOf(listeners[i].type) != -1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Global functions
|
||||
*/
|
||||
|
||||
/*
|
||||
* elementFromPoint
|
||||
*
|
||||
* @param x,y Browser coordinates
|
||||
* @return Element at position, null if no active browser or no element found
|
||||
*/
|
||||
function elementFromPoint(x, y) {
|
||||
// browser's elementFromPoint expect browser-relative client coordinates.
|
||||
// subtract browser's scroll values to adjust
|
||||
let cwu = Util.getWindowUtils(content);
|
||||
let elem = ElementTouchHelper.getClosest(cwu, x, y);
|
||||
|
||||
// step through layers of IFRAMEs and FRAMES to find innermost element
|
||||
while (elem && (elem instanceof HTMLIFrameElement ||
|
||||
elem instanceof HTMLFrameElement)) {
|
||||
// adjust client coordinates' origin to be top left of iframe viewport
|
||||
let rect = elem.getBoundingClientRect();
|
||||
x -= rect.left;
|
||||
y -= rect.top;
|
||||
let windowUtils = elem.contentDocument
|
||||
.defaultView
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
elem = ElementTouchHelper.getClosest(windowUtils, x, y);
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
/*
|
||||
* getBoundingContentRect
|
||||
*
|
||||
* @param aElement
|
||||
* @return Bounding content rect adjusted for scroll and frame offsets.
|
||||
*/
|
||||
function getBoundingContentRect(aElement) {
|
||||
if (!aElement)
|
||||
return new Rect(0, 0, 0, 0);
|
||||
|
||||
let document = aElement.ownerDocument;
|
||||
while(document.defaultView.frameElement)
|
||||
document = document.defaultView.frameElement.ownerDocument;
|
||||
|
||||
let offset = ContentScroll.getScrollOffset(content);
|
||||
offset = new Point(offset.x, offset.y);
|
||||
|
||||
let r = aElement.getBoundingClientRect();
|
||||
|
||||
// step out of iframes and frames, offsetting scroll values
|
||||
let view = aElement.ownerDocument.defaultView;
|
||||
for (let frame = view; frame != content; frame = frame.parent) {
|
||||
// adjust client coordinates' origin to be top left of iframe viewport
|
||||
let rect = frame.frameElement.getBoundingClientRect();
|
||||
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
|
||||
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
|
||||
offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
|
||||
}
|
||||
|
||||
return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* getOverflowContentBoundingRect
|
||||
*
|
||||
* @param aElement
|
||||
* @return Bounding content rect adjusted for scroll and frame offsets.
|
||||
*/
|
||||
|
||||
function getOverflowContentBoundingRect(aElement) {
|
||||
let r = getBoundingContentRect(aElement);
|
||||
|
||||
// If the overflow is hidden don't bother calculating it
|
||||
let computedStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
|
||||
let blockDisplays = ["block", "inline-block", "list-item"];
|
||||
if ((blockDisplays.indexOf(computedStyle.getPropertyValue("display")) != -1 &&
|
||||
computedStyle.getPropertyValue("overflow") == "hidden") ||
|
||||
aElement instanceof HTMLSelectElement) {
|
||||
return r;
|
||||
}
|
||||
|
||||
for (let i = 0; i < aElement.childElementCount; i++) {
|
||||
r = r.union(getBoundingContentRect(aElement.children[i]));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Content
|
||||
*
|
||||
* Browser event receiver for content.
|
||||
*/
|
||||
let Content = {
|
||||
_debugEvents: false,
|
||||
|
||||
get formAssistant() {
|
||||
delete this.formAssistant;
|
||||
return this.formAssistant = new FormAssistant();
|
||||
},
|
||||
|
||||
init: function init() {
|
||||
this._isZoomedToElement = false;
|
||||
|
||||
// Asyncronous messages sent from the browser
|
||||
addMessageListener("Browser:Blur", this);
|
||||
addMessageListener("Browser:SaveAs", this);
|
||||
addMessageListener("Browser:MozApplicationCache:Fetch", this);
|
||||
addMessageListener("Browser:SetCharset", this);
|
||||
addMessageListener("Browser:CanUnload", this);
|
||||
addMessageListener("Browser:PanBegin", this);
|
||||
|
||||
addEventListener("touchstart", this, false);
|
||||
addEventListener("click", this, true);
|
||||
addEventListener("keydown", this);
|
||||
addEventListener("keyup", this);
|
||||
|
||||
// Synchronous events caught during the bubbling phase
|
||||
addEventListener("MozApplicationManifest", this, false);
|
||||
addEventListener("DOMContentLoaded", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
// Attach a listener to watch for "click" events bubbling up from error
|
||||
// pages and other similar page. This lets us fix bugs like 401575 which
|
||||
// require error page UI to do privileged things, without letting error
|
||||
// pages have any privilege themselves.
|
||||
addEventListener("click", this, false);
|
||||
|
||||
docShell.QueryInterface(Ci.nsIDocShellHistory).useGlobalHistory = true;
|
||||
},
|
||||
|
||||
/*******************************************
|
||||
* Events
|
||||
*/
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
if (this._debugEvents) Util.dumpLn("Content:", aEvent.type);
|
||||
switch (aEvent.type) {
|
||||
case "MozApplicationManifest": {
|
||||
let doc = aEvent.originalTarget;
|
||||
sendAsyncMessage("Browser:MozApplicationManifest", {
|
||||
location: doc.documentURIObject.spec,
|
||||
manifest: doc.documentElement.getAttribute("manifest"),
|
||||
charset: doc.characterSet
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "keydown":
|
||||
if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
|
||||
this.formAssistant.close();
|
||||
break;
|
||||
|
||||
case "keyup":
|
||||
// If after a key is pressed we still have no input, then close
|
||||
// the autocomplete. Perhaps the user used backspace or delete.
|
||||
if (!aEvent.target.value)
|
||||
this.formAssistant.close();
|
||||
else
|
||||
this.formAssistant.open(aEvent.target);
|
||||
break;
|
||||
|
||||
case "click":
|
||||
if (aEvent.eventPhase == aEvent.BUBBLING_PHASE)
|
||||
this._onClick(aEvent);
|
||||
else
|
||||
this._genericMouseClick(aEvent);
|
||||
break;
|
||||
|
||||
case "DOMContentLoaded":
|
||||
this._maybeNotifyErroPage();
|
||||
break;
|
||||
|
||||
case "pagehide":
|
||||
if (aEvent.target == content.document)
|
||||
this._resetFontSize();
|
||||
break;
|
||||
|
||||
case "touchstart":
|
||||
let touch = aEvent.changedTouches[0];
|
||||
this._genericMouseDown(touch.clientX, touch.clientY);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
if (this._debugEvents) Util.dumpLn("Content:", aMessage.name);
|
||||
let json = aMessage.json;
|
||||
let x = json.x;
|
||||
let y = json.y;
|
||||
let modifiers = json.modifiers;
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "Browser:Blur":
|
||||
gFocusManager.clearFocus(content);
|
||||
break;
|
||||
|
||||
case "Browser:CanUnload":
|
||||
let canUnload = docShell.contentViewer.permitUnload();
|
||||
sendSyncMessage("Browser:CanUnload:Return", { permit: canUnload });
|
||||
break;
|
||||
|
||||
case "Browser:SaveAs":
|
||||
break;
|
||||
|
||||
case "Browser:MozApplicationCache:Fetch": {
|
||||
let currentURI = Services.io.newURI(json.location, json.charset, null);
|
||||
let manifestURI = Services.io.newURI(json.manifest, json.charset, currentURI);
|
||||
let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
|
||||
.getService(Ci.nsIOfflineCacheUpdateService);
|
||||
updateService.scheduleUpdate(manifestURI, currentURI, content);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Browser:SetCharset": {
|
||||
docShell.charset = json.charset;
|
||||
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Browser:PanBegin":
|
||||
this._cancelTapHighlight();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/******************************************************
|
||||
* generic input handlers
|
||||
*
|
||||
* regardless of whether the input was received via
|
||||
* message manager or sent directly via dispatch.
|
||||
*/
|
||||
|
||||
_genericMouseDown: function _genericMouseDown(x, y) {
|
||||
let element = elementFromPoint(x, y);
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
// There is no need to have a feedback for disabled element
|
||||
let isDisabled = element instanceof HTMLOptionElement ?
|
||||
(element.disabled || element.parentNode.disabled) : element.disabled;
|
||||
if (isDisabled)
|
||||
return;
|
||||
|
||||
// Set the target element to active
|
||||
this._doTapHighlight(element);
|
||||
},
|
||||
|
||||
_genericMouseClick: function _genericMouseClick(aEvent) {
|
||||
ContextMenuHandler.reset();
|
||||
|
||||
let element = elementFromPoint(aEvent.clientX, aEvent.clientY);
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
// Only show autocomplete after the item is clicked
|
||||
if (!this.lastClickElement || this.lastClickElement != element) {
|
||||
this.lastClickElement = element;
|
||||
if (aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE &&
|
||||
!(element instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.formAssistant.focusSync = true;
|
||||
|
||||
// The form manager handles focus related changes on form elements.
|
||||
// If it returns false, it didn't find anything to act on. If the
|
||||
// target element doesn't match the current focus element, clear
|
||||
// focus. This allows users to remove focus from form elements by
|
||||
// taping on white space content.
|
||||
if (!this.formAssistant.open(element, aEvent.clientX, aEvent.clientY)) {
|
||||
if (gFocusManager.focusedElement &&
|
||||
gFocusManager.focusedElement != element) {
|
||||
gFocusManager.focusedElement.blur();
|
||||
}
|
||||
// This may not have any effect if element is unfocusable.
|
||||
gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
|
||||
sendAsyncMessage("FindAssist:Hide", { });
|
||||
}
|
||||
|
||||
// Fire mouse events on everything but selects, see bug 685197
|
||||
if (element instanceof HTMLSelectElement) {
|
||||
aEvent.preventDefault()
|
||||
aEvent.stopPropagation()
|
||||
}
|
||||
this._cancelTapHighlight();
|
||||
this.formAssistant.focusSync = false;
|
||||
},
|
||||
|
||||
/******************************************************
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
// Checks clicks we care about - events bubbling up from about pages.
|
||||
_onClick: function _onClick(aEvent) {
|
||||
// Don't trust synthetic events
|
||||
if (!aEvent.isTrusted)
|
||||
return;
|
||||
|
||||
let ot = aEvent.originalTarget;
|
||||
let errorDoc = ot.ownerDocument;
|
||||
if (!errorDoc)
|
||||
return;
|
||||
|
||||
// If the event came from an ssl error page, it is probably either
|
||||
// "Add Exception…" or "Get me out of here!" button.
|
||||
if (/^about:certerror\?e=nssBadCert/.test(errorDoc.documentURI)) {
|
||||
let perm = errorDoc.getElementById("permanentExceptionButton");
|
||||
let temp = errorDoc.getElementById("temporaryExceptionButton");
|
||||
if (ot == temp || ot == perm) {
|
||||
let action = (ot == perm ? "permanent" : "temporary");
|
||||
sendAsyncMessage("Browser:CertException",
|
||||
{ url: errorDoc.location.href, action: action });
|
||||
} else if (ot == errorDoc.getElementById("getMeOutOfHereButton")) {
|
||||
sendAsyncMessage("Browser:CertException",
|
||||
{ url: errorDoc.location.href, action: "leave" });
|
||||
}
|
||||
} else if (/^about:blocked/.test(errorDoc.documentURI)) {
|
||||
// The event came from a button on a malware/phishing block page
|
||||
// First check whether it's malware or phishing, so that we can
|
||||
// use the right strings/links.
|
||||
let isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
|
||||
|
||||
if (ot == errorDoc.getElementById("getMeOutButton")) {
|
||||
sendAsyncMessage("Browser:BlockedSite",
|
||||
{ url: errorDoc.location.href, action: "leave" });
|
||||
} else if (ot == errorDoc.getElementById("reportButton")) {
|
||||
// This is the "Why is this site blocked" button. For malware,
|
||||
// we can fetch a site-specific report, for phishing, we redirect
|
||||
// to the generic page describing phishing protection.
|
||||
let action = isMalware ? "report-malware" : "report-phising";
|
||||
sendAsyncMessage("Browser:BlockedSite",
|
||||
{ url: errorDoc.location.href, action: action });
|
||||
} else if (ot == errorDoc.getElementById("ignoreWarningButton")) {
|
||||
// Allow users to override and continue through to the site,
|
||||
// but add a notify bar as a reminder, so that they don't lose
|
||||
// track after, e.g., tab switching.
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(content.location,
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
|
||||
null, null, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/******************************************************
|
||||
* General utilities
|
||||
*/
|
||||
|
||||
_getContentClientRects: function getContentClientRects(aElement) {
|
||||
let offset = ContentScroll.getScrollOffset(content);
|
||||
offset = new Point(offset.x, offset.y);
|
||||
|
||||
let nativeRects = aElement.getClientRects();
|
||||
// step out of iframes and frames, offsetting scroll values
|
||||
for (let frame = aElement.ownerDocument.defaultView; frame != content;
|
||||
frame = frame.parent) {
|
||||
// adjust client coordinates' origin to be top left of iframe viewport
|
||||
let rect = frame.frameElement.getBoundingClientRect();
|
||||
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
|
||||
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
|
||||
offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
|
||||
}
|
||||
|
||||
let result = [];
|
||||
for (let i = nativeRects.length - 1; i >= 0; i--) {
|
||||
let r = nativeRects[i];
|
||||
result.push({ left: r.left + offset.x,
|
||||
top: r.top + offset.y,
|
||||
width: r.width,
|
||||
height: r.height
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_maybeNotifyErroPage: function _maybeNotifyErroPage() {
|
||||
// Notify browser that an error page is being shown instead
|
||||
// of the target location. Necessary to get proper thumbnail
|
||||
// updates on chrome for error pages.
|
||||
if (content.location.href !== content.document.documentURI)
|
||||
sendAsyncMessage("Browser:ErrorPage", null);
|
||||
},
|
||||
|
||||
_resetFontSize: function _resetFontSize() {
|
||||
this._isZoomedToElement = false;
|
||||
this._setMinFontSize(0);
|
||||
},
|
||||
|
||||
_highlightElement: null,
|
||||
|
||||
_doTapHighlight: function _doTapHighlight(aElement) {
|
||||
gDOMUtils.setContentState(aElement, kStateActive);
|
||||
this._highlightElement = aElement;
|
||||
},
|
||||
|
||||
_cancelTapHighlight: function _cancelTapHighlight(aElement) {
|
||||
gDOMUtils.setContentState(content.document.documentElement, kStateActive);
|
||||
this._highlightElement = null;
|
||||
},
|
||||
|
||||
/*
|
||||
* _sendMouseEvent
|
||||
*
|
||||
* Delivers mouse events directly to the content window, bypassing
|
||||
* the input overlay.
|
||||
*/
|
||||
_sendMouseEvent: function _sendMouseEvent(aName, aElement, aX, aY, aButton) {
|
||||
// Elements can be off from the aX/aY point because due to touch radius.
|
||||
// If outside, we move the touch point to the center of the element.
|
||||
if (!(aElement instanceof HTMLHtmlElement)) {
|
||||
let isTouchClick = true;
|
||||
let rects = this._getContentClientRects(aElement);
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
let rect = rects[i];
|
||||
// We might be able to deal with fractional pixels, but mouse
|
||||
// events won't. Deflate the bounds in by 1 pixel to deal with
|
||||
// any fractional scroll offset issues.
|
||||
let inBounds =
|
||||
(aX > rect.left + 1 && aX < (rect.left + rect.width - 1)) &&
|
||||
(aY > rect.top + 1 && aY < (rect.top + rect.height - 1));
|
||||
if (inBounds) {
|
||||
isTouchClick = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTouchClick) {
|
||||
let rect = new Rect(rects[0].left, rects[0].top,
|
||||
rects[0].width, rects[0].height);
|
||||
if (rect.isEmpty())
|
||||
return;
|
||||
|
||||
let point = rect.center();
|
||||
aX = point.x;
|
||||
aY = point.y;
|
||||
}
|
||||
}
|
||||
|
||||
let button = aButton || 0;
|
||||
let scrollOffset = ContentScroll.getScrollOffset(content);
|
||||
let x = aX - scrollOffset.x;
|
||||
let y = aY - scrollOffset.y;
|
||||
|
||||
// setting touch source here is important so that when this gets
|
||||
// captured by our precise input detection we can ignore it.
|
||||
let windowUtils = Util.getWindowUtils(content);
|
||||
windowUtils.sendMouseEventToWindow(aName, x, y, button, 1, 0, true,
|
||||
1.0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE);
|
||||
},
|
||||
|
||||
_setMinFontSize: function _setMinFontSize(aSize) {
|
||||
let viewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
|
||||
if (viewer)
|
||||
viewer.minFontSize = aSize;
|
||||
}
|
||||
};
|
||||
|
||||
Content.init();
|
||||
|
||||
var FormSubmitObserver = {
|
||||
init: function init(){
|
||||
addMessageListener("Browser:TabOpen", this);
|
||||
addMessageListener("Browser:TabClose", this);
|
||||
|
||||
addEventListener("pageshow", this, false);
|
||||
|
||||
Services.obs.addObserver(this, "invalidformsubmit", false);
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
let target = aEvent.originalTarget;
|
||||
let isRootDocument = (target == content.document || target.ownerDocument == content.document);
|
||||
if (!isRootDocument)
|
||||
return;
|
||||
|
||||
// Reset invalid submit state on each pageshow
|
||||
if (aEvent.type == "pageshow")
|
||||
Content.formAssistant.invalidSubmit = false;
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "Browser:TabOpen":
|
||||
Services.obs.addObserver(this, "formsubmit", false);
|
||||
break;
|
||||
case "Browser:TabClose":
|
||||
Services.obs.removeObserver(this, "formsubmit", false);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
notify: function notify(aFormElement, aWindow, aActionURI, aCancelSubmit) {
|
||||
// Do not notify unless this is the window where the submit occurred
|
||||
if (aWindow == content)
|
||||
// We don't need to send any data along
|
||||
sendAsyncMessage("Browser:FormSubmit", {});
|
||||
},
|
||||
|
||||
notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) {
|
||||
if (!aInvalidElements.length)
|
||||
return;
|
||||
|
||||
let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
|
||||
if (!(element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLTextAreaElement ||
|
||||
element instanceof HTMLSelectElement ||
|
||||
element instanceof HTMLButtonElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Content.formAssistant.invalidSubmit = true;
|
||||
Content.formAssistant.open(element);
|
||||
},
|
||||
|
||||
QueryInterface : function(aIID) {
|
||||
if (!aIID.equals(Ci.nsIFormSubmitObserver) &&
|
||||
!aIID.equals(Ci.nsISupportsWeakReference) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
FormSubmitObserver.init();
|
|
@ -0,0 +1,360 @@
|
|||
/* 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/. */
|
||||
|
||||
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
|
||||
|
||||
dump("### ContextMenuHandler.js loaded\n");
|
||||
|
||||
var ContextMenuHandler = {
|
||||
_types: [],
|
||||
|
||||
init: function ch_init() {
|
||||
// Events we catch from content during the bubbling phase
|
||||
addEventListener("contextmenu", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
addEventListener("select", this, false);
|
||||
|
||||
// Messages we receive from browser
|
||||
addMessageListener("Browser:ContextCommand", this, false);
|
||||
|
||||
this.popupNode = null;
|
||||
},
|
||||
|
||||
_getLinkURL: function ch_getLinkURL(aLink) {
|
||||
let href = aLink.href;
|
||||
if (href)
|
||||
return href;
|
||||
|
||||
href = aLink.getAttributeNS(kXLinkNamespace, "href");
|
||||
if (!href || !href.match(/\S/)) {
|
||||
// Without this we try to save as the current doc,
|
||||
// for example, HTML case also throws if empty
|
||||
throw "Empty href";
|
||||
}
|
||||
|
||||
return Util.makeURLAbsolute(aLink.baseURI, href);
|
||||
},
|
||||
|
||||
_getURI: function ch_getURI(aURL) {
|
||||
try {
|
||||
return Util.makeURI(aURL);
|
||||
} catch (ex) { }
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
_getProtocol: function ch_getProtocol(aURI) {
|
||||
if (aURI)
|
||||
return aURI.scheme;
|
||||
return null;
|
||||
},
|
||||
|
||||
handleEvent: function ch_handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "contextmenu":
|
||||
this._onContentContextMenu(aEvent);
|
||||
break;
|
||||
case "pagehide":
|
||||
this.reset();
|
||||
break;
|
||||
case "select":
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function ch_receiveMessage(aMessage) {
|
||||
let node = this.popupNode;
|
||||
let command = aMessage.json.command;
|
||||
|
||||
switch (command) {
|
||||
case "play":
|
||||
case "pause":
|
||||
if (node instanceof Ci.nsIDOMHTMLMediaElement)
|
||||
node[command]();
|
||||
break;
|
||||
|
||||
case "videotab":
|
||||
if (node instanceof Ci.nsIDOMHTMLVideoElement) {
|
||||
node.pause();
|
||||
Cu.import("resource:///modules/video.jsm");
|
||||
Video.fullScreenSourceElement = node;
|
||||
sendAsyncMessage("Browser:FullScreenVideo:Start");
|
||||
}
|
||||
break;
|
||||
|
||||
case "select-all":
|
||||
this._onSelectAll();
|
||||
break;
|
||||
|
||||
case "paste":
|
||||
this._onPaste();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/******************************************************
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
reset: function ch_reset() {
|
||||
this.popupNode = null;
|
||||
this._target = null;
|
||||
},
|
||||
|
||||
// content contextmenu
|
||||
_onContentContextMenu: function _onContentContextMenu(aEvent) {
|
||||
if (aEvent.defaultPrevented)
|
||||
return;
|
||||
|
||||
// Don't let these bubble up to input.js
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
|
||||
let state = {
|
||||
types: [],
|
||||
label: "",
|
||||
linkURL: "",
|
||||
linkTitle: "",
|
||||
linkProtocol: null,
|
||||
mediaURL: "",
|
||||
x: aEvent.x,
|
||||
y: aEvent.y
|
||||
};
|
||||
|
||||
let popupNode = this.popupNode = aEvent.originalTarget;
|
||||
let imageUrl = "";
|
||||
|
||||
// Do checks for nodes that never have children.
|
||||
if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
|
||||
// See if the user clicked on an image.
|
||||
if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
|
||||
state.types.push("image");
|
||||
state.label = state.mediaURL = popupNode.currentURI.spec;
|
||||
imageUrl = state.mediaURL;
|
||||
|
||||
// Retrieve the type of image from the cache since the url can fail to
|
||||
// provide valuable informations
|
||||
try {
|
||||
let imageCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
|
||||
let props = imageCache.findEntryProperties(popupNode.currentURI,
|
||||
content.document.characterSet);
|
||||
if (props) {
|
||||
state.contentType = String(props.get("type", Ci.nsISupportsCString));
|
||||
state.contentDisposition = String(props.get("content-disposition",
|
||||
Ci.nsISupportsCString));
|
||||
}
|
||||
} catch (ex) {
|
||||
Util.dumpLn(ex.message);
|
||||
// Failure to get type and content-disposition off the image is non-fatal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let elem = popupNode;
|
||||
let isText = false;
|
||||
|
||||
while (elem) {
|
||||
if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
|
||||
// is the target a link or a descendant of a link?
|
||||
if (this._isLink(elem)) {
|
||||
// If this is an image that links to itself, don't include both link and
|
||||
// image otpions.
|
||||
if (imageUrl == this._getLinkURL(elem)) {
|
||||
elem = elem.parentNode;
|
||||
continue;
|
||||
}
|
||||
|
||||
state.types.push("link");
|
||||
state.label = state.linkURL = this._getLinkURL(elem);
|
||||
linkUrl = state.linkURL;
|
||||
state.linkTitle = popupNode.textContent || popupNode.title;
|
||||
state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
|
||||
break;
|
||||
} else if (this._isTextInput(elem)) {
|
||||
let selectionStart = elem.selectionStart;
|
||||
let selectionEnd = elem.selectionEnd;
|
||||
|
||||
state.types.push("input-text");
|
||||
this._target = elem;
|
||||
|
||||
// Don't include "copy" for password fields.
|
||||
if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) {
|
||||
if (selectionStart != selectionEnd) {
|
||||
state.types.push("copy");
|
||||
state.string = elem.value.slice(selectionStart, selectionEnd);
|
||||
} else if (elem.value) {
|
||||
state.types.push("copy-all");
|
||||
state.string = elem.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionStart > 0 || selectionEnd < elem.textLength) {
|
||||
state.types.push("select-all");
|
||||
}
|
||||
|
||||
if (!elem.textLength) {
|
||||
state.types.push("input-empty");
|
||||
}
|
||||
|
||||
let flavors = ["text/unicode"];
|
||||
let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
let hasData = cb.hasDataMatchingFlavors(flavors,
|
||||
flavors.length,
|
||||
Ci.nsIClipboard.kGlobalClipboard);
|
||||
if (hasData && !elem.readOnly) {
|
||||
state.types.push("paste");
|
||||
}
|
||||
break;
|
||||
} else if (this._isText(elem)) {
|
||||
isText = true;
|
||||
} else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
|
||||
elem instanceof Ci.nsIDOMHTMLVideoElement) {
|
||||
state.label = state.mediaURL = (elem.currentSrc || elem.src);
|
||||
state.types.push((elem.paused || elem.ended) ?
|
||||
"media-paused" : "media-playing");
|
||||
if (elem instanceof Ci.nsIDOMHTMLVideoElement) {
|
||||
state.types.push("video");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elem = elem.parentNode;
|
||||
}
|
||||
|
||||
if (isText) {
|
||||
// If this is text and has a selection, we want to bring
|
||||
// up the copy option on the context menu.
|
||||
if (content && content.getSelection() &&
|
||||
content.getSelection().toString().length > 0) {
|
||||
state.string = content.getSelection().toString();
|
||||
state.types.push("copy");
|
||||
state.types.push("selected-text");
|
||||
} else {
|
||||
// Add general content text if this isn't anything specific
|
||||
if (state.types.indexOf("image") == -1 &&
|
||||
state.types.indexOf("media") == -1 &&
|
||||
state.types.indexOf("video") == -1 &&
|
||||
state.types.indexOf("link") == -1 &&
|
||||
state.types.indexOf("input-text") == -1) {
|
||||
state.types.push("content-text");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// populate position and event source
|
||||
state.xPos = aEvent.clientX;
|
||||
state.yPos = aEvent.clientY;
|
||||
state.source = aEvent.mozInputSource;
|
||||
|
||||
for (let i = 0; i < this._types.length; i++)
|
||||
if (this._types[i].handler(state, popupNode))
|
||||
state.types.push(this._types[i].name);
|
||||
|
||||
sendAsyncMessage("Content:ContextMenu", state);
|
||||
},
|
||||
|
||||
_onSelectAll: function _onSelectAll() {
|
||||
if (this._isTextInput(this._target)) {
|
||||
// select all text in the input control
|
||||
this._target.select();
|
||||
} else {
|
||||
// select the entire document
|
||||
content.getSelection().selectAllChildren(content.document);
|
||||
}
|
||||
this.reset();
|
||||
},
|
||||
|
||||
_onPaste: function _onPaste() {
|
||||
// paste text if this is an input control
|
||||
if (this._isTextInput(this._target)) {
|
||||
let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
|
||||
if (edit) {
|
||||
edit.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
|
||||
} else {
|
||||
Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
|
||||
}
|
||||
}
|
||||
this.reset();
|
||||
},
|
||||
|
||||
/*
|
||||
* Utility routines used in testing for various
|
||||
* HTML element types.
|
||||
*/
|
||||
|
||||
_isTextInput: function _isTextInput(element) {
|
||||
return ((element instanceof Ci.nsIDOMHTMLInputElement &&
|
||||
element.mozIsTextField(false)) ||
|
||||
element instanceof Ci.nsIDOMHTMLTextAreaElement);
|
||||
},
|
||||
|
||||
_isLink: function _isLink(element) {
|
||||
return ((element instanceof Ci.nsIDOMHTMLAnchorElement && element.href) ||
|
||||
(element instanceof Ci.nsIDOMHTMLAreaElement && element.href) ||
|
||||
element instanceof Ci.nsIDOMHTMLLinkElement ||
|
||||
element.getAttributeNS(kXLinkNamespace, "type") == "simple");
|
||||
},
|
||||
|
||||
_isText: function _isText(element) {
|
||||
return (element instanceof Ci.nsIDOMHTMLParagraphElement ||
|
||||
element instanceof Ci.nsIDOMHTMLDivElement ||
|
||||
element instanceof Ci.nsIDOMHTMLLIElement ||
|
||||
element instanceof Ci.nsIDOMHTMLPreElement ||
|
||||
element instanceof Ci.nsIDOMHTMLHeadingElement ||
|
||||
element instanceof Ci.nsIDOMHTMLTableCellElement ||
|
||||
element instanceof Ci.nsIDOMHTMLBodyElement);
|
||||
},
|
||||
|
||||
/**
|
||||
* For add-ons to add new types and data to the ContextMenu message.
|
||||
*
|
||||
* @param aName A string to identify the new type.
|
||||
* @param aHandler A function that takes a state object and a target element.
|
||||
* If aHandler returns true, then aName will be added to the list of types.
|
||||
* The function may also modify the state object.
|
||||
*/
|
||||
registerType: function registerType(aName, aHandler) {
|
||||
this._types.push({name: aName, handler: aHandler});
|
||||
},
|
||||
|
||||
/** Remove all handlers registered for a given type. */
|
||||
unregisterType: function unregisterType(aName) {
|
||||
this._types = this._types.filter(function(type) type.name != aName);
|
||||
}
|
||||
};
|
||||
|
||||
ContextMenuHandler.init();
|
||||
|
||||
ContextMenuHandler.registerType("mailto", function(aState, aElement) {
|
||||
return aState.linkProtocol == "mailto";
|
||||
});
|
||||
|
||||
ContextMenuHandler.registerType("callto", function(aState, aElement) {
|
||||
let protocol = aState.linkProtocol;
|
||||
return protocol == "tel" || protocol == "callto" || protocol == "sip" || protocol == "voipto";
|
||||
});
|
||||
|
||||
ContextMenuHandler.registerType("link-openable", function(aState, aElement) {
|
||||
return Util.isOpenableScheme(aState.linkProtocol);
|
||||
});
|
||||
|
||||
["image", "video"].forEach(function(aType) {
|
||||
ContextMenuHandler.registerType(aType+"-shareable", function(aState, aElement) {
|
||||
if (aState.types.indexOf(aType) == -1)
|
||||
return false;
|
||||
|
||||
let protocol = ContextMenuHandler._getProtocol(ContextMenuHandler._getURI(aState.mediaURL));
|
||||
return Util.isShareableScheme(protocol);
|
||||
});
|
||||
});
|
||||
|
||||
ContextMenuHandler.registerType("image-loaded", function(aState, aElement) {
|
||||
if (aState.types.indexOf("image") != -1) {
|
||||
let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* 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/. */
|
||||
|
||||
dump("### FindHandler.js loaded\n");
|
||||
|
||||
var FindHandler = {
|
||||
get _fastFind() {
|
||||
delete this._fastFind;
|
||||
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
|
||||
this._fastFind.init(docShell);
|
||||
return this._fastFind;
|
||||
},
|
||||
|
||||
init: function findHandlerInit() {
|
||||
addMessageListener("FindAssist:Find", this);
|
||||
addMessageListener("FindAssist:Next", this);
|
||||
addMessageListener("FindAssist:Previous", this);
|
||||
},
|
||||
|
||||
receiveMessage: function findHandlerReceiveMessage(aMessage) {
|
||||
let findResult = Ci.nsITypeAheadFind.FIND_NOTFOUND;
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "FindAssist:Find":
|
||||
findResult = this._fastFind.find(json.searchString, false);
|
||||
break;
|
||||
|
||||
case "FindAssist:Previous":
|
||||
findResult = this._fastFind.findAgain(true, false);
|
||||
break;
|
||||
|
||||
case "FindAssist:Next":
|
||||
findResult = this._fastFind.findAgain(false, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (findResult == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
||||
sendAsyncMessage("FindAssist:Show", { rect: null , result: findResult });
|
||||
return;
|
||||
}
|
||||
|
||||
let selection = this._fastFind.currentWindow.getSelection();
|
||||
if (!selection.rangeCount || selection.isCollapsed) {
|
||||
// The selection can be into an input or a textarea element
|
||||
let nodes = content.document.querySelectorAll("input[type='text'], textarea");
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
|
||||
selection = node.editor.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
|
||||
if (selection.rangeCount && !selection.isCollapsed)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let scroll = ContentScroll.getScrollOffset(content);
|
||||
for (let frame = this._fastFind.currentWindow; frame != content; frame = frame.parent) {
|
||||
let rect = frame.frameElement.getBoundingClientRect();
|
||||
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
|
||||
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
|
||||
scroll.add(rect.left + parseInt(left), rect.top + parseInt(top));
|
||||
}
|
||||
|
||||
let rangeRect = selection.getRangeAt(0).getBoundingClientRect();
|
||||
let rect = new Rect(scroll.x + rangeRect.left, scroll.y + rangeRect.top, rangeRect.width, rangeRect.height);
|
||||
|
||||
// Ensure the potential "scroll" event fired during a search as already fired
|
||||
let timer = new Util.Timeout(function() {
|
||||
sendAsyncMessage("FindAssist:Show", { rect: rect.isEmpty() ? null: rect , result: findResult });
|
||||
});
|
||||
timer.once(0);
|
||||
}
|
||||
};
|
||||
|
||||
FindHandler.init();
|
|
@ -0,0 +1,972 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
|
||||
dump("### FormHelper.js loaded\n");
|
||||
|
||||
const kPrefFormHelperEnabled = "formhelper.enabled";
|
||||
|
||||
let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
|
||||
let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
|
||||
let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
|
||||
let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
|
||||
let HTMLDocument = Ci.nsIDOMHTMLDocument;
|
||||
let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
|
||||
let HTMLBodyElement = Ci.nsIDOMHTMLBodyElement;
|
||||
let HTMLLabelElement = Ci.nsIDOMHTMLLabelElement;
|
||||
let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
|
||||
let HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement;
|
||||
let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
|
||||
let XULMenuListElement = Ci.nsIDOMXULMenuListElement;
|
||||
|
||||
/**
|
||||
* Responsible of navigation between forms fields and of the opening of the assistant
|
||||
*/
|
||||
function FormAssistant() {
|
||||
addMessageListener("FormAssist:Closed", this);
|
||||
addMessageListener("FormAssist:Previous", this);
|
||||
addMessageListener("FormAssist:Next", this);
|
||||
addMessageListener("FormAssist:ChoiceSelect", this);
|
||||
addMessageListener("FormAssist:ChoiceChange", this);
|
||||
addMessageListener("FormAssist:AutoComplete", this);
|
||||
addMessageListener("Content:SetWindowSize", this);
|
||||
|
||||
/* Listen text events in order to update the autocomplete suggestions as soon
|
||||
* a key is entered on device
|
||||
*/
|
||||
addEventListener("text", this, false);
|
||||
|
||||
addEventListener("keypress", this, true);
|
||||
addEventListener("keyup", this, false);
|
||||
addEventListener("focus", this, true);
|
||||
addEventListener("blur", this, true);
|
||||
addEventListener("pageshow", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
addEventListener("submit", this, false);
|
||||
|
||||
this._enabled = Services.prefs.prefHasUserValue(kPrefFormHelperEnabled) ?
|
||||
Services.prefs.getBoolPref(kPrefFormHelperEnabled) : false;
|
||||
};
|
||||
|
||||
FormAssistant.prototype = {
|
||||
_debugEvents: false,
|
||||
_selectWrapper: null,
|
||||
_currentIndex: -1,
|
||||
_elements: [],
|
||||
|
||||
invalidSubmit: false,
|
||||
|
||||
get currentElement() {
|
||||
return this._elements[this._currentIndex];
|
||||
},
|
||||
|
||||
get currentIndex() {
|
||||
return this._currentIndex;
|
||||
},
|
||||
|
||||
set currentIndex(aIndex) {
|
||||
let element = this._elements[aIndex];
|
||||
if (!element)
|
||||
return -1;
|
||||
|
||||
if (this._isVisibleElement(element)) {
|
||||
this._currentIndex = aIndex;
|
||||
gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
|
||||
|
||||
// To ensure we get the current caret positionning of the focused
|
||||
// element we need to delayed a bit the event
|
||||
this._executeDelayed(function(self) {
|
||||
// Bug 640870
|
||||
// Sometimes the element inner frame get destroyed while the element
|
||||
// receive the focus because the display is turned to 'none' for
|
||||
// example, in this "fun" case just do nothing if the element is hidden
|
||||
if (self._isVisibleElement(gFocusManager.focusedElement))
|
||||
sendAsyncMessage("FormAssist:Show", self._getJSON());
|
||||
});
|
||||
} else {
|
||||
// Repopulate the list of elements in the page, some could have gone
|
||||
// because of AJAX changes for example
|
||||
this._elements = [];
|
||||
let currentIndex = this._getAllElements(gFocusManager.focusedElement)
|
||||
|
||||
if (aIndex < this._currentIndex)
|
||||
this.currentIndex = currentIndex - 1;
|
||||
else if (aIndex > this._currentIndex)
|
||||
this.currentIndex = currentIndex + 1;
|
||||
else if (this._currentIndex != currentIndex)
|
||||
this.currentIndex = currentIndex;
|
||||
}
|
||||
return element;
|
||||
},
|
||||
|
||||
_open: false,
|
||||
open: function formHelperOpen(aElement, aX, aY) {
|
||||
// If the click is on an option element we want to check if the parent
|
||||
// is a valid target.
|
||||
if (aElement instanceof HTMLOptionElement &&
|
||||
aElement.parentNode instanceof HTMLSelectElement &&
|
||||
!aElement.disabled) {
|
||||
aElement = aElement.parentNode;
|
||||
}
|
||||
|
||||
// The form assistant will close if a click happen:
|
||||
// * outside of the scope of the form helper
|
||||
// * hover a button of type=[image|submit]
|
||||
// * hover a disabled element
|
||||
if (!this._isValidElement(aElement)) {
|
||||
let passiveButtons = { button: true, checkbox: true, file: true, radio: true, reset: true };
|
||||
if ((aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement) &&
|
||||
passiveButtons[aElement.type] && !aElement.disabled)
|
||||
return false;
|
||||
|
||||
// Check for plugins element
|
||||
if (aElement instanceof Ci.nsIDOMHTMLEmbedElement) {
|
||||
aX = aX || 0;
|
||||
aY = aY || 0;
|
||||
this._executeDelayed(function(self) {
|
||||
let utils = Util.getWindowUtils(aElement.ownerDocument.defaultView);
|
||||
if (utils.IMEStatus == utils.IME_STATUS_PLUGIN) {
|
||||
let jsvar = {
|
||||
current: {
|
||||
id: aElement.id,
|
||||
name: aElement.name,
|
||||
title: "plugin",
|
||||
value: null,
|
||||
maxLength: 0,
|
||||
type: (aElement.getAttribute("type") || "").toLowerCase(),
|
||||
choices: null,
|
||||
isAutocomplete: false,
|
||||
validationMessage: null,
|
||||
list: null,
|
||||
rect: getBoundingContentRect(aElement),
|
||||
caretRect: new Rect(aX, aY, 1, 10),
|
||||
editable: true
|
||||
},
|
||||
hasPrevious: false,
|
||||
hasNext: false
|
||||
};
|
||||
sendAsyncMessage("FormAssist:Show", jsvar);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return this.close();
|
||||
}
|
||||
|
||||
// Look for a top editable element
|
||||
if (this._isEditable(aElement))
|
||||
aElement = this._getTopLevelEditable(aElement);
|
||||
|
||||
// There are some cases where we still want data to be sent to FormHelperUI
|
||||
// even if form assistant is disabled:
|
||||
// - the element is a choice list
|
||||
// - the element has autocomplete suggestions
|
||||
this._enabled = Services.prefs.prefHasUserValue(kPrefFormHelperEnabled) ?
|
||||
Services.prefs.getBoolPref(kPrefFormHelperEnabled) : false;
|
||||
if (!this._enabled && !this._isSelectElement(aElement) && !this._isAutocomplete(aElement)) {
|
||||
return this.close();
|
||||
}
|
||||
|
||||
if (this._enabled) {
|
||||
this._elements = [];
|
||||
this.currentIndex = this._getAllElements(aElement);
|
||||
} else {
|
||||
this._elements = [aElement];
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
|
||||
return this._open = true;
|
||||
},
|
||||
|
||||
close: function close() {
|
||||
if (this._open) {
|
||||
this._currentIndex = -1;
|
||||
this._elements = [];
|
||||
sendAsyncMessage("FormAssist:Hide", { });
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
return this._open;
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
if (this._debugEvents) Util.dumpLn(aMessage.name);
|
||||
let currentElement = this.currentElement;
|
||||
if ((!this._enabled && !this._isAutocomplete(currentElement) && !getWrapperForElement(currentElement)) || !currentElement)
|
||||
return;
|
||||
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "FormAssist:Previous":
|
||||
this.currentIndex--;
|
||||
break;
|
||||
|
||||
case "FormAssist:Next":
|
||||
this.currentIndex++;
|
||||
break;
|
||||
|
||||
case "Content:SetWindowSize":
|
||||
// If the CSS viewport change just show the current element to the new
|
||||
// position
|
||||
sendAsyncMessage("FormAssist:Resize", this._getJSON());
|
||||
break;
|
||||
|
||||
case "FormAssist:ChoiceSelect": {
|
||||
this._selectWrapper = getWrapperForElement(currentElement);
|
||||
this._selectWrapper.select(json.index, json.selected);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAssist:ChoiceChange": {
|
||||
// ChoiceChange could happened once we have move to an other element or
|
||||
// to nothing, so we should keep the used wrapper in mind
|
||||
this._selectWrapper.fireOnChange();
|
||||
|
||||
// New elements can be shown when a select is updated so we need to
|
||||
// reconstruct the inner elements array and to take care of possible
|
||||
// focus change, this is why we use "self.currentElement" instead of
|
||||
// using directly "currentElement".
|
||||
this._executeDelayed(function(self) {
|
||||
let currentElement = self.currentElement;
|
||||
if (!currentElement)
|
||||
return;
|
||||
|
||||
self._elements = [];
|
||||
self._currentIndex = self._getAllElements(currentElement);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAssist:AutoComplete": {
|
||||
try {
|
||||
currentElement = currentElement.QueryInterface(Ci.nsIDOMNSEditableElement);
|
||||
let imeEditor = currentElement.editor.QueryInterface(Ci.nsIEditorIMESupport);
|
||||
if (imeEditor.composing)
|
||||
imeEditor.forceCompositionEnd();
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
currentElement.value = json.value;
|
||||
|
||||
let event = currentElement.ownerDocument.createEvent("Events");
|
||||
event.initEvent("DOMAutoComplete", true, true);
|
||||
currentElement.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAssist:Closed":
|
||||
currentElement.blur();
|
||||
this._currentIndex = null;
|
||||
this._open = false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_els: Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService),
|
||||
_hasKeyListener: function _hasKeyListener(aElement) {
|
||||
let els = this._els;
|
||||
let listeners = els.getListenerInfoFor(aElement, {});
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
let listener = listeners[i];
|
||||
if (["keyup", "keydown", "keypress"].indexOf(listener.type) != -1
|
||||
&& !listener.inSystemEventGroup) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
focusSync: false,
|
||||
handleEvent: function formHelperHandleEvent(aEvent) {
|
||||
if (this._debugEvents) Util.dumpLn(aEvent.type, this.currentElement);
|
||||
// focus changes should be taken into account only if the user has done a
|
||||
// manual operation like manually clicking
|
||||
let shouldIgnoreFocus = (aEvent.type == "focus" && !this._open && !this.focusSync);
|
||||
if ((!this._open && aEvent.type != "focus") || shouldIgnoreFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentElement = this.currentElement;
|
||||
switch (aEvent.type) {
|
||||
case "submit":
|
||||
// submit is a final action and the form assistant should be closed
|
||||
this.close();
|
||||
break;
|
||||
|
||||
case "pagehide":
|
||||
case "pageshow":
|
||||
// When reacting to a page show/hide, if the focus is different this
|
||||
// could mean the web page has dramatically changed because of
|
||||
// an Ajax change based on fragment identifier
|
||||
if (gFocusManager.focusedElement != currentElement)
|
||||
this.close();
|
||||
break;
|
||||
|
||||
case "focus":
|
||||
let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {}) || aEvent.target;
|
||||
|
||||
// If a body element is editable and the body is the child of an
|
||||
// iframe we can assume this is an advanced HTML editor, so let's
|
||||
// redirect the form helper selection to the iframe element
|
||||
if (focusedElement && this._isEditable(focusedElement)) {
|
||||
let editableElement = this._getTopLevelEditable(focusedElement);
|
||||
if (this._isValidElement(editableElement)) {
|
||||
this._executeDelayed(function(self) {
|
||||
self.open(editableElement);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if an element is focused while we're closed but the element can be handle
|
||||
// by the assistant, try to activate it (only during mouseup)
|
||||
if (!currentElement) {
|
||||
if (focusedElement && this._isValidElement(focusedElement)) {
|
||||
this._executeDelayed(function(self) {
|
||||
self.open(focusedElement);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let focusedIndex = this._getIndexForElement(focusedElement);
|
||||
if (focusedIndex != -1 && this.currentIndex != focusedIndex)
|
||||
this.currentIndex = focusedIndex;
|
||||
break;
|
||||
|
||||
case "blur":
|
||||
content.setTimeout(function(self) {
|
||||
if (!self._open)
|
||||
return;
|
||||
|
||||
// If the blurring causes focus be in no other element,
|
||||
// we should close the form assistant.
|
||||
let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
|
||||
if (!focusedElement)
|
||||
self.close();
|
||||
}, 0, this);
|
||||
break;
|
||||
|
||||
case "text":
|
||||
if (this._isValidatable(aEvent.target))
|
||||
sendAsyncMessage("FormAssist:ValidationMessage", this._getJSON());
|
||||
|
||||
if (this._isAutocomplete(aEvent.target))
|
||||
sendAsyncMessage("FormAssist:AutoComplete", this._getJSON());
|
||||
break;
|
||||
|
||||
// key processing inside a select element are done during the keypress
|
||||
// handler, preventing this one to be fired cancel the selection change
|
||||
case "keypress":
|
||||
// There is no need to handle keys if there is not element currently
|
||||
// used by the form assistant
|
||||
if (!currentElement)
|
||||
return;
|
||||
|
||||
let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true };
|
||||
if (this._isSelectElement(currentElement) || formExceptions[currentElement.type] ||
|
||||
currentElement instanceof HTMLButtonElement || (currentElement.getAttribute("role") == "button" && currentElement.hasAttribute("tabindex"))) {
|
||||
switch (aEvent.keyCode) {
|
||||
case aEvent.DOM_VK_RIGHT:
|
||||
this._executeDelayed(function(self) {
|
||||
self.currentIndex++;
|
||||
});
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
|
||||
case aEvent.DOM_VK_LEFT:
|
||||
this._executeDelayed(function(self) {
|
||||
self.currentIndex--;
|
||||
});
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "keyup":
|
||||
// There is no need to handle keys if there is not element currently
|
||||
// used by the form assistant
|
||||
if (!currentElement)
|
||||
return;
|
||||
|
||||
switch (aEvent.keyCode) {
|
||||
case aEvent.DOM_VK_DOWN:
|
||||
if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
|
||||
if (this._hasKeyListener(currentElement))
|
||||
return;
|
||||
} else if (currentElement instanceof HTMLTextAreaElement) {
|
||||
let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
|
||||
let isEnd = (currentElement.textLength == currentElement.selectionEnd);
|
||||
if (!isEnd || existSelection)
|
||||
return;
|
||||
} else if (getListForElement(currentElement)) {
|
||||
this.currentIndex = this.currentIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentIndex++;
|
||||
break;
|
||||
|
||||
case aEvent.DOM_VK_UP:
|
||||
if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
|
||||
if (this._hasKeyListener(currentElement))
|
||||
return;
|
||||
} else if (currentElement instanceof HTMLTextAreaElement) {
|
||||
let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
|
||||
let isStart = (currentElement.selectionEnd == 0);
|
||||
if (!isStart || existSelection)
|
||||
return;
|
||||
} else if (this._isSelectElement(currentElement)) {
|
||||
this.currentIndex = this.currentIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentIndex--;
|
||||
break;
|
||||
|
||||
case aEvent.DOM_VK_RETURN:
|
||||
if (!this._isVisibleElement(currentElement))
|
||||
this.close();
|
||||
break;
|
||||
|
||||
case aEvent.DOM_VK_ESCAPE:
|
||||
case aEvent.DOM_VK_TAB:
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this._isValidatable(aEvent.target)) {
|
||||
sendAsyncMessage("FormAssist:ValidationMessage", this._getJSON());
|
||||
}
|
||||
|
||||
if (this._isAutocomplete(aEvent.target)) {
|
||||
sendAsyncMessage("FormAssist:AutoComplete", this._getJSON());
|
||||
} else if (currentElement && this._isSelectElement(currentElement)) {
|
||||
this.currentIndex = this.currentIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let caretRect = this._getCaretRect();
|
||||
if (!caretRect.isEmpty())
|
||||
sendAsyncMessage("FormAssist:Update", { caretRect: caretRect });
|
||||
}
|
||||
},
|
||||
|
||||
_executeDelayed: function formHelperExecuteSoon(aCallback) {
|
||||
let self = this;
|
||||
let timer = new Util.Timeout(function() {
|
||||
aCallback(self);
|
||||
});
|
||||
timer.once(0);
|
||||
},
|
||||
|
||||
_filterEditables: function formHelperFilterEditables(aNodes) {
|
||||
let result = [];
|
||||
for (let i = 0; i < aNodes.length; i++) {
|
||||
let node = aNodes[i];
|
||||
|
||||
// Avoid checking the top level editable element of each node
|
||||
if (this._isEditable(node)) {
|
||||
let editableElement = this._getTopLevelEditable(node);
|
||||
if (result.indexOf(editableElement) == -1)
|
||||
result.push(editableElement);
|
||||
}
|
||||
else {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_isEditable: function formHelperIsEditable(aElement) {
|
||||
let canEdit = false;
|
||||
|
||||
if (aElement.isContentEditable || aElement.designMode == "on") {
|
||||
canEdit = true;
|
||||
} else if (aElement instanceof HTMLIFrameElement && (aElement.contentDocument.body.isContentEditable || aElement.contentDocument.designMode == "on")) {
|
||||
canEdit = true;
|
||||
} else {
|
||||
canEdit = aElement.ownerDocument && aElement.ownerDocument.designMode == "on";
|
||||
}
|
||||
|
||||
return canEdit;
|
||||
},
|
||||
|
||||
_getTopLevelEditable: function formHelperGetTopLevelEditable(aElement) {
|
||||
if (!(aElement instanceof HTMLIFrameElement)) {
|
||||
let element = aElement;
|
||||
|
||||
// Retrieve the top element that is editable
|
||||
if (element instanceof HTMLHtmlElement)
|
||||
element = element.ownerDocument.body;
|
||||
else if (element instanceof HTMLDocument)
|
||||
element = element.body;
|
||||
|
||||
while (element && !this._isEditable(element))
|
||||
element = element.parentNode;
|
||||
|
||||
// Return the container frame if we are into a nested editable frame
|
||||
if (element && element instanceof HTMLBodyElement && element.ownerDocument.defaultView != content.document.defaultView)
|
||||
return element.ownerDocument.defaultView.frameElement;
|
||||
}
|
||||
|
||||
return aElement;
|
||||
},
|
||||
|
||||
_isValidatable: function(aElement) {
|
||||
return this.invalidSubmit &&
|
||||
(aElement instanceof HTMLInputElement ||
|
||||
aElement instanceof HTMLTextAreaElement ||
|
||||
aElement instanceof HTMLSelectElement ||
|
||||
aElement instanceof HTMLButtonElement);
|
||||
},
|
||||
|
||||
_isAutocomplete: function formHelperIsAutocomplete(aElement) {
|
||||
if (aElement instanceof HTMLInputElement) {
|
||||
if (aElement.getAttribute("type") == "password")
|
||||
return false;
|
||||
|
||||
let autocomplete = aElement.getAttribute("autocomplete");
|
||||
let allowedValues = ["off", "false", "disabled"];
|
||||
if (allowedValues.indexOf(autocomplete) == -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* This function is similar to getListSuggestions from
|
||||
* components/satchel/src/nsInputListAutoComplete.js but sadly this one is
|
||||
* used by the autocomplete.xml binding which is not in used in fennec
|
||||
*/
|
||||
_getListSuggestions: function formHelperGetListSuggestions(aElement) {
|
||||
if (!(aElement instanceof HTMLInputElement) || !aElement.list)
|
||||
return [];
|
||||
|
||||
let suggestions = [];
|
||||
let filter = !aElement.hasAttribute("mozNoFilter");
|
||||
let lowerFieldValue = aElement.value.toLowerCase();
|
||||
|
||||
let options = aElement.list.options;
|
||||
let length = options.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
let item = options.item(i);
|
||||
|
||||
let label = item.value;
|
||||
if (item.label)
|
||||
label = item.label;
|
||||
else if (item.text)
|
||||
label = item.text;
|
||||
|
||||
if (filter && label.toLowerCase().indexOf(lowerFieldValue) == -1)
|
||||
continue;
|
||||
suggestions.push({ label: label, value: item.value });
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
},
|
||||
|
||||
_isValidElement: function formHelperIsValidElement(aElement) {
|
||||
if (!aElement.getAttribute)
|
||||
return false;
|
||||
|
||||
let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true };
|
||||
if (aElement instanceof HTMLInputElement && formExceptions[aElement.type])
|
||||
return false;
|
||||
|
||||
if (aElement instanceof HTMLButtonElement ||
|
||||
(aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex")))
|
||||
return false;
|
||||
|
||||
return this._isNavigableElement(aElement) && this._isVisibleElement(aElement);
|
||||
},
|
||||
|
||||
_isNavigableElement: function formHelperIsNavigableElement(aElement) {
|
||||
if (aElement.disabled || aElement.getAttribute("tabindex") == "-1")
|
||||
return false;
|
||||
|
||||
if (aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex"))
|
||||
return true;
|
||||
|
||||
if (this._isSelectElement(aElement) || aElement instanceof HTMLTextAreaElement)
|
||||
return true;
|
||||
|
||||
if (aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement)
|
||||
return !(aElement.type == "hidden");
|
||||
|
||||
return this._isEditable(aElement);
|
||||
},
|
||||
|
||||
_isVisibleElement: function formHelperIsVisibleElement(aElement) {
|
||||
let style = aElement ? aElement.ownerDocument.defaultView.getComputedStyle(aElement, null) : null;
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
let isVisible = (style.getPropertyValue("visibility") != "hidden");
|
||||
let isOpaque = (style.getPropertyValue("opacity") != 0);
|
||||
|
||||
let rect = aElement.getBoundingClientRect();
|
||||
|
||||
// Since the only way to show a drop-down menu for a select when the form
|
||||
// assistant is enabled is to return true here, a select is allowed to have
|
||||
// an opacity to 0 in order to let web developpers add a custom design on
|
||||
// top of it. This is less important to use the form assistant for the
|
||||
// other types of fields because even if the form assistant won't fired,
|
||||
// the focus will be in and a VKB will popup if needed
|
||||
return isVisible && (isOpaque || this._isSelectElement(aElement)) && (rect.height != 0 || rect.width != 0);
|
||||
},
|
||||
|
||||
_isSelectElement: function formHelperIsSelectElement(aElement) {
|
||||
return (aElement instanceof HTMLSelectElement || aElement instanceof XULMenuListElement);
|
||||
},
|
||||
|
||||
/** Caret is used to input text for this element. */
|
||||
_getCaretRect: function _formHelperGetCaretRect() {
|
||||
let element = this.currentElement;
|
||||
let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
|
||||
if (element && (element.mozIsTextField && element.mozIsTextField(false) ||
|
||||
element instanceof HTMLTextAreaElement) && focusedElement == element && this._isVisibleElement(element)) {
|
||||
let utils = Util.getWindowUtils(element.ownerDocument.defaultView);
|
||||
let rect = utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, element.selectionEnd, 0, 0, 0);
|
||||
if (rect) {
|
||||
let scroll = ContentScroll.getScrollOffset(element.ownerDocument.defaultView);
|
||||
return new Rect(scroll.x + rect.left, scroll.y + rect.top, rect.width, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
return new Rect(0, 0, 0, 0);
|
||||
},
|
||||
|
||||
/** Gets a rect bounding important parts of the element that must be seen when assisting. */
|
||||
_getRect: function _formHelperGetRect() {
|
||||
const kDistanceMax = 100;
|
||||
let element = this.currentElement;
|
||||
let elRect = getBoundingContentRect(element);
|
||||
let labels = this._getLabels();
|
||||
for (let i=0; i<labels.length; i++) {
|
||||
let labelRect = labels[i].rect;
|
||||
if (labelRect.left < elRect.left) {
|
||||
let isClose = Math.abs(labelRect.left - elRect.left) - labelRect.width < kDistanceMax &&
|
||||
Math.abs(labelRect.top - elRect.top) - labelRect.height < kDistanceMax;
|
||||
if (isClose) {
|
||||
let width = labelRect.width + elRect.width + (elRect.left - labelRect.left - labelRect.width);
|
||||
return new Rect(labelRect.left, labelRect.top, width, elRect.height).expandToIntegers();
|
||||
}
|
||||
}
|
||||
}
|
||||
return elRect;
|
||||
},
|
||||
|
||||
_getLabels: function formHelperGetLabels() {
|
||||
let associatedLabels = [];
|
||||
|
||||
let element = this.currentElement;
|
||||
let labels = element.ownerDocument.getElementsByTagName("label");
|
||||
for (let i=0; i<labels.length; i++) {
|
||||
let label = labels[i];
|
||||
if ((label.control == element || label.getAttribute("for") == element.id) && this._isVisibleElement(label)) {
|
||||
associatedLabels.push({
|
||||
rect: getBoundingContentRect(label),
|
||||
title: label.textContent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return associatedLabels;
|
||||
},
|
||||
|
||||
_getAllElements: function getAllElements(aElement) {
|
||||
// XXX good candidate for tracing if possible.
|
||||
// The tough ones are lenght and isVisibleElement.
|
||||
let document = aElement.ownerDocument;
|
||||
if (!document)
|
||||
return;
|
||||
|
||||
let documents = Util.getAllDocuments(document);
|
||||
|
||||
let elements = this._elements;
|
||||
for (let i = 0; i < documents.length; i++) {
|
||||
let selector = "input, button, select, textarea, [role=button], iframe, [contenteditable=true]";
|
||||
let nodes = documents[i].querySelectorAll(selector);
|
||||
nodes = this._filterRadioButtons(nodes);
|
||||
|
||||
for (let j = 0; j < nodes.length; j++) {
|
||||
let node = nodes[j];
|
||||
if (!this._isNavigableElement(node) || !this._isVisibleElement(node))
|
||||
continue;
|
||||
|
||||
elements.push(node);
|
||||
}
|
||||
}
|
||||
this._elements = this._filterEditables(elements);
|
||||
|
||||
function orderByTabIndex(a, b) {
|
||||
// for an explanation on tabbing navigation see
|
||||
// http://www.w3.org/TR/html401/interact/forms.html#h-17.11.1
|
||||
// In resume tab index navigation order is 1, 2, 3, ..., 32767, 0
|
||||
if (a.tabIndex == 0 || b.tabIndex == 0)
|
||||
return b.tabIndex;
|
||||
|
||||
return a.tabIndex > b.tabIndex;
|
||||
}
|
||||
this._elements = this._elements.sort(orderByTabIndex);
|
||||
|
||||
// retrieve the correct index
|
||||
let currentIndex = this._getIndexForElement(aElement);
|
||||
return currentIndex;
|
||||
},
|
||||
|
||||
_getIndexForElement: function(aElement) {
|
||||
let currentIndex = -1;
|
||||
let elements = this._elements;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i] == aElement)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
_getJSON: function() {
|
||||
let element = this.currentElement;
|
||||
let choices = getListForElement(element);
|
||||
let editable = (element instanceof HTMLInputElement && element.mozIsTextField(false)) || this._isEditable(element);
|
||||
|
||||
let labels = this._getLabels();
|
||||
return {
|
||||
current: {
|
||||
id: element.id,
|
||||
name: element.name,
|
||||
title: labels.length ? labels[0].title : "",
|
||||
value: element.value,
|
||||
maxLength: element.maxLength,
|
||||
type: (element.getAttribute("type") || "").toLowerCase(),
|
||||
choices: choices,
|
||||
isAutocomplete: this._isAutocomplete(element),
|
||||
validationMessage: this.invalidSubmit ? element.validationMessage : null,
|
||||
list: this._getListSuggestions(element),
|
||||
rect: this._getRect(),
|
||||
caretRect: this._getCaretRect(),
|
||||
editable: editable
|
||||
},
|
||||
hasPrevious: !!this._elements[this._currentIndex - 1],
|
||||
hasNext: !!this._elements[this._currentIndex + 1]
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* For each radio button group, remove all but the checked button
|
||||
* if there is one, or the first button otherwise.
|
||||
*/
|
||||
_filterRadioButtons: function(aNodes) {
|
||||
// First pass: Find the checked or first element in each group.
|
||||
let chosenRadios = {};
|
||||
for (let i=0; i < aNodes.length; i++) {
|
||||
let node = aNodes[i];
|
||||
if (node.type == "radio" && (!chosenRadios.hasOwnProperty(node.name) || node.checked))
|
||||
chosenRadios[node.name] = node;
|
||||
}
|
||||
|
||||
// Second pass: Exclude all other radio buttons from the list.
|
||||
let result = [];
|
||||
for (let i=0; i < aNodes.length; i++) {
|
||||
let node = aNodes[i];
|
||||
if (node.type == "radio" && chosenRadios[node.name] != node)
|
||||
continue;
|
||||
result.push(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* The next classes wraps some forms elements such as different type of list to
|
||||
* abstract the difference between html and xul element while manipulating them
|
||||
* - SelectWrapper : <html:select>
|
||||
* - MenulistWrapper : <xul:menulist>
|
||||
*****************************************************************************/
|
||||
|
||||
function getWrapperForElement(aElement) {
|
||||
let wrapper = null;
|
||||
if (aElement instanceof HTMLSelectElement) {
|
||||
wrapper = new SelectWrapper(aElement);
|
||||
}
|
||||
else if (aElement instanceof XULMenuListElement) {
|
||||
wrapper = new MenulistWrapper(aElement);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function getListForElement(aElement) {
|
||||
let wrapper = getWrapperForElement(aElement);
|
||||
if (!wrapper)
|
||||
return null;
|
||||
|
||||
let optionIndex = 0;
|
||||
let result = {
|
||||
multiple: wrapper.getMultiple(),
|
||||
choices: []
|
||||
};
|
||||
|
||||
// Build up a flat JSON array of the choices. In HTML, it's possible for select element choices
|
||||
// to be under a group header (but not recursively). We distinguish between headers and entries
|
||||
// using the boolean "list.group".
|
||||
// XXX If possible, this would be a great candidate for tracing.
|
||||
let children = wrapper.getChildren();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
if (wrapper.isGroup(child)) {
|
||||
// This is the group element. Add an entry in the choices that says that the following
|
||||
// elements are a member of this group.
|
||||
result.choices.push({ group: true,
|
||||
text: child.label || child.firstChild.data,
|
||||
disabled: child.disabled
|
||||
});
|
||||
let subchildren = child.children;
|
||||
for (let j = 0; j < subchildren.length; j++) {
|
||||
let subchild = subchildren[j];
|
||||
result.choices.push({
|
||||
group: false,
|
||||
inGroup: true,
|
||||
text: wrapper.getText(subchild),
|
||||
disabled: child.disabled || subchild.disabled,
|
||||
selected: subchild.selected,
|
||||
optionIndex: optionIndex++
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (wrapper.isOption(child)) {
|
||||
// This is a regular choice under no group.
|
||||
result.choices.push({
|
||||
group: false,
|
||||
inGroup: false,
|
||||
text: wrapper.getText(child),
|
||||
disabled: child.disabled,
|
||||
selected: child.selected,
|
||||
optionIndex: optionIndex++
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
function SelectWrapper(aControl) {
|
||||
this._control = aControl;
|
||||
}
|
||||
|
||||
SelectWrapper.prototype = {
|
||||
getSelectedIndex: function() {
|
||||
return this._control.selectedIndex;
|
||||
},
|
||||
|
||||
getMultiple: function() {
|
||||
return this._control.multiple;
|
||||
},
|
||||
|
||||
getOptions: function() {
|
||||
return this._control.options;
|
||||
},
|
||||
|
||||
getChildren: function() {
|
||||
return this._control.children;
|
||||
},
|
||||
|
||||
getText: function(aChild) {
|
||||
return aChild.text;
|
||||
},
|
||||
|
||||
isOption: function(aChild) {
|
||||
return aChild instanceof HTMLOptionElement;
|
||||
},
|
||||
|
||||
isGroup: function(aChild) {
|
||||
return aChild instanceof HTMLOptGroupElement;
|
||||
},
|
||||
|
||||
select: function(aIndex, aSelected) {
|
||||
let options = this._control.options;
|
||||
if (this.getMultiple())
|
||||
options[aIndex].selected = aSelected;
|
||||
else
|
||||
options.selectedIndex = aIndex;
|
||||
},
|
||||
|
||||
fireOnChange: function() {
|
||||
let control = this._control;
|
||||
let evt = this._control.ownerDocument.createEvent("Events");
|
||||
evt.initEvent("change", true, true, this._control.ownerDocument.defaultView, 0,
|
||||
false, false,
|
||||
false, false, null);
|
||||
content.setTimeout(function() {
|
||||
control.dispatchEvent(evt);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// bug 559792
|
||||
// Use wrappedJSObject when control is in content for extra protection
|
||||
function MenulistWrapper(aControl) {
|
||||
this._control = aControl;
|
||||
}
|
||||
|
||||
MenulistWrapper.prototype = {
|
||||
getSelectedIndex: function() {
|
||||
let control = this._control.wrappedJSObject || this._control;
|
||||
let result = control.selectedIndex;
|
||||
return ((typeof result == "number" && !isNaN(result)) ? result : -1);
|
||||
},
|
||||
|
||||
getMultiple: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
getOptions: function() {
|
||||
let control = this._control.wrappedJSObject || this._control;
|
||||
return control.menupopup.children;
|
||||
},
|
||||
|
||||
getChildren: function() {
|
||||
let control = this._control.wrappedJSObject || this._control;
|
||||
return control.menupopup.children;
|
||||
},
|
||||
|
||||
getText: function(aChild) {
|
||||
return aChild.label;
|
||||
},
|
||||
|
||||
isOption: function(aChild) {
|
||||
return aChild instanceof Ci.nsIDOMXULSelectControlItemElement;
|
||||
},
|
||||
|
||||
isGroup: function(aChild) {
|
||||
return false;
|
||||
},
|
||||
|
||||
select: function(aIndex, aSelected) {
|
||||
let control = this._control.wrappedJSObject || this._control;
|
||||
control.selectedIndex = aIndex;
|
||||
},
|
||||
|
||||
fireOnChange: function() {
|
||||
let control = this._control;
|
||||
let evt = document.createEvent("XULCommandEvent");
|
||||
evt.initCommandEvent("command", true, true, window, 0,
|
||||
false, false,
|
||||
false, false, null);
|
||||
content.setTimeout(function() {
|
||||
control.dispatchEvent(evt);
|
||||
}, 0);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/* 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/. */
|
||||
|
||||
var PluginCTPHandler = {
|
||||
init: function() {
|
||||
addEventListener("PluginClickToPlay", this, false);
|
||||
},
|
||||
|
||||
addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
|
||||
// XXX just doing (callback)(arg) was giving a same-origin error. bug?
|
||||
let self = this;
|
||||
let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
|
||||
linkNode.addEventListener("click",
|
||||
function(evt) {
|
||||
if (!evt.isTrusted)
|
||||
return;
|
||||
evt.preventDefault();
|
||||
if (callbackArgs.length == 0)
|
||||
callbackArgs = [ evt ];
|
||||
(self[callbackName]).apply(self, callbackArgs);
|
||||
},
|
||||
true);
|
||||
|
||||
linkNode.addEventListener("keydown",
|
||||
function(evt) {
|
||||
if (!evt.isTrusted)
|
||||
return;
|
||||
if (evt.keyCode == evt.DOM_VK_RETURN) {
|
||||
evt.preventDefault();
|
||||
if (callbackArgs.length == 0)
|
||||
callbackArgs = [ evt ];
|
||||
evt.preventDefault();
|
||||
(self[callbackName]).apply(self, callbackArgs);
|
||||
}
|
||||
},
|
||||
true);
|
||||
},
|
||||
|
||||
handleEvent : function(event) {
|
||||
if (event.type != "PluginClickToPlay")
|
||||
return;
|
||||
let plugin = event.target;
|
||||
PluginHandler.addLinkClickCallback(plugin, "reloadToEnablePlugin");
|
||||
},
|
||||
|
||||
reloadToEnablePlugin: function() {
|
||||
sendAsyncMessage("Browser:PluginClickToPlayClicked", { });
|
||||
}
|
||||
};
|
||||
|
||||
//PluginCTPHandler.init();
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,136 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
dump("### ViewportHandler.js loaded\n");
|
||||
|
||||
// See getViewportMetadata. Blindly copied from Safari
|
||||
// documentation for now.
|
||||
const kViewportMinScale = 0;
|
||||
const kViewportMaxScale = 10;
|
||||
const kViewportMinWidth = 200;
|
||||
const kViewportMaxWidth = 10000;
|
||||
const kViewportMinHeight = 223;
|
||||
const kViewportMaxHeight = 10000;
|
||||
|
||||
/*
|
||||
* ViewportHandler
|
||||
*
|
||||
* Plucks zoom info out of web page metadata and forwards it to the
|
||||
* browser.
|
||||
*/
|
||||
|
||||
let ViewportHandler = {
|
||||
init: function init() {
|
||||
addEventListener("DOMWindowCreated", this, false);
|
||||
addEventListener("DOMMetaAdded", this, false);
|
||||
addEventListener("DOMContentLoaded", this, false);
|
||||
addEventListener("pageshow", this, false);
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
let target = aEvent.originalTarget;
|
||||
let isRootDocument = (target == content.document || target.ownerDocument == content.document);
|
||||
if (!isRootDocument)
|
||||
return;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "DOMWindowCreated":
|
||||
this.resetMetadata();
|
||||
break;
|
||||
|
||||
case "DOMMetaAdded":
|
||||
if (target.name == "viewport")
|
||||
this.updateMetadata();
|
||||
break;
|
||||
|
||||
case "DOMContentLoaded":
|
||||
case "pageshow":
|
||||
this.updateMetadata();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
resetMetadata: function resetMetadata() {
|
||||
sendAsyncMessage("Browser:ViewportMetadata", null);
|
||||
},
|
||||
|
||||
updateMetadata: function updateMetadata() {
|
||||
sendAsyncMessage("Browser:ViewportMetadata", this.getViewportMetadata());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with the page's preferred viewport properties:
|
||||
* defaultZoom (optional float): The initial scale when the page is loaded.
|
||||
* minZoom (optional float): The minimum zoom level.
|
||||
* maxZoom (optional float): The maximum zoom level.
|
||||
* width (optional int): The CSS viewport width in px.
|
||||
* height (optional int): The CSS viewport height in px.
|
||||
* autoSize (boolean): Resize the CSS viewport when the window resizes.
|
||||
* allowZoom (boolean): Let the user zoom in or out.
|
||||
* autoScale (boolean): Adjust the viewport properties to account for display density.
|
||||
*/
|
||||
getViewportMetadata: function getViewportMetadata() {
|
||||
let doctype = content.document.doctype;
|
||||
if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))
|
||||
return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
|
||||
|
||||
let windowUtils = Util.getWindowUtils(content);
|
||||
let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
|
||||
if (handheldFriendly == "true")
|
||||
return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
|
||||
|
||||
if (content.document instanceof XULDocument)
|
||||
return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
|
||||
|
||||
// HACK: Since we can't set the scale in local tabs (bug 597081), we force
|
||||
// them to device-width and scale=1 so they will lay out reasonably.
|
||||
if (Util.isLocalScheme(content.document.documentURI))
|
||||
return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
|
||||
|
||||
// HACK: Since we can't set the scale correctly in frameset pages yet (bug 645756), we force
|
||||
// them to device-width and scale=1 so they will lay out reasonably.
|
||||
if (content.frames.length > 0 && (content.document.body instanceof HTMLFrameSetElement))
|
||||
return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
|
||||
|
||||
// viewport details found here
|
||||
// http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html
|
||||
// http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html
|
||||
|
||||
// Note: These values will be NaN if parseFloat or parseInt doesn't find a number.
|
||||
// Remember that NaN is contagious: Math.max(1, NaN) == Math.min(1, NaN) == NaN.
|
||||
let scale = parseFloat(windowUtils.getDocumentMetadata("viewport-initial-scale"));
|
||||
let minScale = parseFloat(windowUtils.getDocumentMetadata("viewport-minimum-scale"));
|
||||
let maxScale = parseFloat(windowUtils.getDocumentMetadata("viewport-maximum-scale"));
|
||||
|
||||
let widthStr = windowUtils.getDocumentMetadata("viewport-width");
|
||||
let heightStr = windowUtils.getDocumentMetadata("viewport-height");
|
||||
let width = Util.clamp(parseInt(widthStr), kViewportMinWidth, kViewportMaxWidth);
|
||||
let height = Util.clamp(parseInt(heightStr), kViewportMinHeight, kViewportMaxHeight);
|
||||
|
||||
let allowZoomStr = windowUtils.getDocumentMetadata("viewport-user-scalable");
|
||||
let allowZoom = !/^(0|no|false)$/.test(allowZoomStr); // WebKit allows 0, "no", or "false"
|
||||
|
||||
scale = Util.clamp(scale, kViewportMinScale, kViewportMaxScale);
|
||||
minScale = Util.clamp(minScale, kViewportMinScale, kViewportMaxScale);
|
||||
maxScale = Util.clamp(maxScale, kViewportMinScale, kViewportMaxScale);
|
||||
|
||||
// If initial scale is 1.0 and width is not set, assume width=device-width
|
||||
let autoSize = (widthStr == "device-width" ||
|
||||
(!widthStr && (heightStr == "device-height" || scale == 1.0)));
|
||||
|
||||
return {
|
||||
defaultZoom: scale,
|
||||
minZoom: minScale,
|
||||
maxZoom: maxScale,
|
||||
width: width,
|
||||
height: height,
|
||||
autoSize: autoSize,
|
||||
allowZoom: allowZoom,
|
||||
autoScale: true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
ViewportHandler.init();
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/* 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/. */
|
||||
|
||||
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
|
||||
@namespace html url(http://www.w3.org/1999/xhtml);
|
||||
|
||||
xul|*:-moz-system-metric(touch-enabled) {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
html|*:-moz-system-metric(touch-enabled) {
|
||||
cursor: none !important;
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png";
|
||||
|
||||
var Downloads = {
|
||||
_inited: false,
|
||||
_progressAlert: null,
|
||||
|
||||
get manager() {
|
||||
return Cc["@mozilla.org/download-manager;1"]
|
||||
.getService(Ci.nsIDownloadManager);
|
||||
},
|
||||
|
||||
_getReferrerOrSource: function dh__getReferrerOrSource(aDownload) {
|
||||
return aDownload.referrer.spec || aDownload.source.spec;
|
||||
},
|
||||
|
||||
_getLocalFile: function dh__getLocalFile(aFileURI) {
|
||||
// XXX it's possible that using a null char-set here is bad
|
||||
|
||||
const fileUrl = Services.io.newURI(aFileURI.spec || aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
|
||||
return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
|
||||
},
|
||||
|
||||
init: function dh_init() {
|
||||
if (this._inited)
|
||||
return;
|
||||
|
||||
this._inited = true;
|
||||
|
||||
Services.obs.addObserver(this, "dl-start", true);
|
||||
Services.obs.addObserver(this, "dl-done", true);
|
||||
},
|
||||
|
||||
uninit: function dh_uninit() {
|
||||
Services.obs.removeObserver(this, "dl-start");
|
||||
Services.obs.removeObserver(this, "dl-done");
|
||||
},
|
||||
|
||||
openDownload: function dh_openDownload(aDownload) {
|
||||
// expects xul item
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
let download = this.manager.getDownload(id)
|
||||
let fileURI = download.target;
|
||||
let file = this._getLocalFile(fileURI);
|
||||
|
||||
try {
|
||||
file.launch();
|
||||
} catch (ex) { }
|
||||
},
|
||||
|
||||
removeDownload: function dh_removeDownload(aDownload) {
|
||||
// aDownload is the XUL element here,
|
||||
// and .target currently returns the target attribute (string value)
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
let download = this.manager.getDownload(id);
|
||||
|
||||
if (download) {
|
||||
// TODO <sfoster> add class/mark element for removal transition?
|
||||
this.manager.removeDownload(id);
|
||||
}
|
||||
},
|
||||
|
||||
cancelDownload: function dh_cancelDownload(aDownload) {
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
let download = this.manager.getDownload(id)
|
||||
this.manager.cancelDownload(id);
|
||||
|
||||
let fileURI = download.target;
|
||||
let file = this._getLocalFile(fileURI);
|
||||
|
||||
if (file.exists())
|
||||
file.remove(false);
|
||||
},
|
||||
|
||||
pauseDownload: function dh_pauseDownload(aDownload) {
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
this.manager.pauseDownload(id);
|
||||
},
|
||||
|
||||
resumeDownload: function dh_resumeDownload(aDownload) {
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
this.manager.resumeDownload(id);
|
||||
},
|
||||
|
||||
retryDownload: function dh_retryDownload(aDownload) {
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
this.manager.retryDownload(id);
|
||||
},
|
||||
|
||||
showPage: function dh_showPage(aDownload) {
|
||||
let id = aDownload.getAttribute("downloadId");
|
||||
let download = this.manager.getDownload(id)
|
||||
let uri = this._getReferrerOrSource(download);
|
||||
if (uri)
|
||||
BrowserUI.newTab(uri, Browser.selectedTab);
|
||||
},
|
||||
|
||||
showAlert: function dh_showAlert(aName, aMessage, aTitle, aIcon) {
|
||||
var notifier = Cc["@mozilla.org/alerts-service;1"]
|
||||
.getService(Ci.nsIAlertsService);
|
||||
|
||||
// Callback for tapping on the alert popup
|
||||
let observer = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
if (aTopic == "alertclickcallback")
|
||||
PanelUI.show("downloads-container");
|
||||
}
|
||||
};
|
||||
|
||||
if (!aTitle)
|
||||
aTitle = Strings.browser.GetStringFromName("alertDownloads");
|
||||
|
||||
if (!aIcon)
|
||||
aIcon = URI_GENERIC_ICON_DOWNLOAD;
|
||||
|
||||
notifier.showAlertNotification(aIcon, aTitle, aMessage, true, "", observer, aName);
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
let download = aSubject.QueryInterface(Ci.nsIDownload);
|
||||
let msgKey = "";
|
||||
|
||||
switch (aTopic) {
|
||||
case "dl-start":
|
||||
msgKey = "alertDownloadsStart";
|
||||
if (!this._progressAlert) {
|
||||
this._progressAlert = new AlertDownloadProgressListener();
|
||||
this.manager.addListener(this._progressAlert);
|
||||
}
|
||||
break;
|
||||
case "dl-done":
|
||||
msgKey = "alertDownloadsDone";
|
||||
break;
|
||||
}
|
||||
|
||||
if (msgKey) {
|
||||
let message = Strings.browser.formatStringFromName(msgKey, [download.displayName], 1);
|
||||
let url = download.target.spec.replace("file:", "download:");
|
||||
this.showAlert(url, message);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function (aIID) {
|
||||
if (!aIID.equals(Ci.nsIObserver) &&
|
||||
!aIID.equals(Ci.nsISupportsWeakReference) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
|
||||
* fills it with the user's downloads. It observes download notifications,
|
||||
* so it'll automatically update to reflect current download progress.
|
||||
*
|
||||
* @param aSet Control implementing nsIDOMXULSelectControlElement.
|
||||
* @param {Number} aLimit Maximum number of items to show in the view.
|
||||
*/
|
||||
function DownloadsView(aSet, aLimit) {
|
||||
this._set = aSet;
|
||||
this._limit = aLimit;
|
||||
|
||||
this._progress = new DownloadProgressListener(this);
|
||||
Downloads.manager.addListener(this._progress);
|
||||
|
||||
// Look for the removed download notification
|
||||
let obs = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
obs.addObserver(this, "download-manager-remove-download-guid", false);
|
||||
|
||||
this.getDownloads();
|
||||
}
|
||||
|
||||
DownloadsView.prototype = {
|
||||
_progress: null,
|
||||
_stmt: null,
|
||||
_timeoutID: null,
|
||||
_set: null,
|
||||
_limit: null,
|
||||
|
||||
_getItemForDownloadGuid: function dv__getItemForDownload(aGuid) {
|
||||
return this._set.querySelector("richgriditem[downloadGuid='" + aGuid + "']");
|
||||
},
|
||||
|
||||
_getItemForDownload: function dv__getItemForDownload(aDownload) {
|
||||
return this._set.querySelector("richgriditem[downloadId='" + aDownload.id + "']");
|
||||
},
|
||||
|
||||
_getDownloadForItem: function dv__getDownloadForItem(anItem) {
|
||||
let id = anItem.getAttribute("downloadId");
|
||||
return Downloads.manager.getDownload(id);
|
||||
},
|
||||
|
||||
_getAttrsForDownload: function dv__getAttrsForDownload(aDownload) {
|
||||
// expects a DownloadManager download object
|
||||
return {
|
||||
typeName: 'download',
|
||||
downloadId: aDownload.id,
|
||||
downloadGuid: aDownload.guid,
|
||||
name: aDownload.displayName,
|
||||
target: aDownload.target,
|
||||
iconURI: "moz-icon://" + aDownload.displayName + "?size=64",
|
||||
date: DownloadUtils.getReadableDates(new Date())[0],
|
||||
domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
|
||||
size: this._getDownloadSize(aDownload.size),
|
||||
state: aDownload.state
|
||||
};
|
||||
|
||||
},
|
||||
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||||
for (let name in aAttrs)
|
||||
anItem.setAttribute(name, aAttrs[name]);
|
||||
},
|
||||
|
||||
_getDownloadSize: function dv__getDownloadSize (aSize) {
|
||||
let displaySize = DownloadUtils.convertByteUnits(aSize);
|
||||
if (displaySize[0] > 0) // [0] is size, [1] is units
|
||||
return displaySize.join("");
|
||||
else
|
||||
return Strings.browser.GetStringFromName("downloadsUnknownSize");
|
||||
},
|
||||
|
||||
_initStatement: function dv__initStatement() {
|
||||
if (this._stmt)
|
||||
this._stmt.finalize();
|
||||
|
||||
let limitClause = this._limit ? ("LIMIT " + this._limit) : "";
|
||||
|
||||
this._stmt = Downloads.manager.DBConnection.createStatement(
|
||||
"SELECT id, guid, name, target, source, state, startTime, endTime, referrer, " +
|
||||
"currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
|
||||
"FROM moz_downloads " +
|
||||
"ORDER BY isActive DESC, endTime DESC, startTime DESC " +
|
||||
limitClause);
|
||||
},
|
||||
|
||||
_stepDownloads: function dv__stepDownloads(aNumItems) {
|
||||
try {
|
||||
if (!this._stmt.executeStep()) {
|
||||
// final record;
|
||||
this._stmt.finalize();
|
||||
this._stmt = null;
|
||||
this._fire("DownloadsReady", this._set);
|
||||
return;
|
||||
}
|
||||
let attrs = {
|
||||
typeName: 'download',
|
||||
// TODO: <sfoster> the remove event gives us guid not id; should we use guid as the download (record) id?
|
||||
downloadGuid: this._stmt.row.guid,
|
||||
downloadId: this._stmt.row.id,
|
||||
name: this._stmt.row.name,
|
||||
target: this._stmt.row.target,
|
||||
iconURI: "moz-icon://" + this._stmt.row.name + "?size=25",
|
||||
date: DownloadUtils.getReadableDates(new Date(this._stmt.row.endTime / 1000))[0],
|
||||
domain: DownloadUtils.getURIHost(this._stmt.row.source)[0],
|
||||
size: this._getDownloadSize(this._stmt.row.maxBytes),
|
||||
state: this._stmt.row.state
|
||||
};
|
||||
|
||||
let item = this._set.appendItem(attrs.target, attrs.downloadId);
|
||||
this._updateItemWithAttrs(item, attrs);
|
||||
} catch (e) {
|
||||
// Something went wrong when stepping or getting values, so quit
|
||||
this._stmt.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add another item to the list if we should;
|
||||
// otherwise, let the UI update and continue later
|
||||
if (aNumItems > 1) {
|
||||
this._stepDownloads(aNumItems - 1);
|
||||
} else {
|
||||
// Use a shorter delay for earlier downloads to display them faster
|
||||
let delay = Math.min(this._set.itemCount * 10, 300);
|
||||
let self = this;
|
||||
this._timeoutID = setTimeout(function() { self._stepDownloads(5); }, delay);
|
||||
}
|
||||
},
|
||||
|
||||
_fire: function _fire(aName, anElement) {
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent(aName, true, true);
|
||||
anElement.dispatchEvent(event);
|
||||
},
|
||||
|
||||
observe: function dv_managerObserver(aSubject, aTopic, aData) {
|
||||
// observer-service message handler
|
||||
switch (aTopic) {
|
||||
case "download-manager-remove-download-guid":
|
||||
let guid = aSubject.QueryInterface(Ci.nsISupportsCString);
|
||||
this.removeDownload({
|
||||
guid: guid
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
getDownloads: function dv_getDownloads() {
|
||||
this._initStatement();
|
||||
clearTimeout(this._timeoutID);
|
||||
|
||||
// Since we're pulling in all downloads, clear the list to avoid duplication
|
||||
this.clearDownloads();
|
||||
|
||||
this._stmt.reset();
|
||||
this._stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
|
||||
this._stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
|
||||
this._stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
|
||||
this._stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
|
||||
this._stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
|
||||
|
||||
// Take a quick break before we actually start building the list
|
||||
let self = this;
|
||||
this._timeoutID = setTimeout(function() {
|
||||
self._stepDownloads(1);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
clearDownloads: function dv_clearDownloads() {
|
||||
while (this._set.itemCount > 0)
|
||||
this._set.removeItemAt(0);
|
||||
},
|
||||
|
||||
addDownload: function dv_addDownload(aDownload) {
|
||||
// expects a download manager download object
|
||||
let attrs = this._getAttrsForDownload(aDownload);
|
||||
let item = this._set.insertItemAt(0, attrs.target, attrs.downloadId);
|
||||
this._updateItemWithAttrs(item, attrs);
|
||||
},
|
||||
|
||||
updateDownload: function dv_updateDownload(aDownload) {
|
||||
// expects a download manager download object
|
||||
let item = this._getItemForDownload(aDownload);
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
let attrs = this._getAttrsForDownload(aDownload);
|
||||
this._updateItemWithAttrs(item, attrs);
|
||||
},
|
||||
|
||||
removeDownload: function dv_removeDownload(aDownload) {
|
||||
// expects an object with id or guid property
|
||||
let item;
|
||||
if (aDownload.id) {
|
||||
item = this._getItemForDownload(aDownload.id);
|
||||
} else if (aDownload.guid) {
|
||||
item = this._getItemForDownloadGuid(aDownload.guid);
|
||||
}
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
let idx = this._set.getIndexOfItem(item);
|
||||
if (idx < 0)
|
||||
return;
|
||||
// any transition needed here?
|
||||
this._set.removeItemAt(idx);
|
||||
},
|
||||
|
||||
destruct: function dv_destruct() {
|
||||
Downloads.manager.removeListener(this._progress);
|
||||
}
|
||||
};
|
||||
|
||||
var DownloadsPanelView = {
|
||||
_view: null,
|
||||
|
||||
get _grid() { return document.getElementById("downloads-list"); },
|
||||
get visible() { return PanelUI.isPaneVisible("downloads-container"); },
|
||||
|
||||
init: function init() {
|
||||
this._view = new DownloadsView(this._grid);
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies a DownloadsView about updates in the state of various downloads.
|
||||
*
|
||||
* @param aView An instance of DownloadsView.
|
||||
*/
|
||||
function DownloadProgressListener(aView) {
|
||||
this._view = aView;
|
||||
}
|
||||
|
||||
DownloadProgressListener.prototype = {
|
||||
_view: null,
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDownloadProgressListener
|
||||
onDownloadStateChange: function dPL_onDownloadStateChange(aState, aDownload) {
|
||||
let state = aDownload.state;
|
||||
switch (state) {
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY:
|
||||
this._view.addDownload(aDownload);
|
||||
break;
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
|
||||
break;
|
||||
}
|
||||
|
||||
this._view.updateDownload(aDownload);
|
||||
},
|
||||
|
||||
onProgressChange: function dPL_onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
|
||||
// TODO <jwilde>: Add more detailed progress information.
|
||||
this._view.updateDownload(aDownload);
|
||||
},
|
||||
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||||
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsISupports
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tracks download progress so that additional information can be displayed
|
||||
* about its download in alert popups.
|
||||
*/
|
||||
function AlertDownloadProgressListener() { }
|
||||
|
||||
AlertDownloadProgressListener.prototype = {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDownloadProgressListener
|
||||
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
|
||||
let strings = Strings.browser;
|
||||
let availableSpace = -1;
|
||||
|
||||
try {
|
||||
// diskSpaceAvailable is not implemented on all systems
|
||||
let availableSpace = aDownload.targetFile.diskSpaceAvailable;
|
||||
} catch(ex) { }
|
||||
|
||||
let contentLength = aDownload.size;
|
||||
if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
|
||||
Downloads.showAlert(aDownload.target.spec.replace("file:", "download:"),
|
||||
strings.GetStringFromName("alertDownloadsNoSpace"),
|
||||
strings.GetStringFromName("alertDownloadsSize"));
|
||||
Downloads.cancelDownload(aDownload.id);
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadStateChange: function(aState, aDownload) { },
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||||
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsISupports
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
|
||||
};
|
|
@ -0,0 +1,115 @@
|
|||
/* 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/. */
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
/**
|
||||
A class to add exceptions to override SSL certificate problems. The functionality
|
||||
itself is borrowed from exceptionDialog.js.
|
||||
*/
|
||||
function SSLExceptions() {
|
||||
this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
|
||||
.getService(Ci.nsICertOverrideService);
|
||||
}
|
||||
|
||||
|
||||
SSLExceptions.prototype = {
|
||||
_overrideService: null,
|
||||
_sslStatus: null,
|
||||
|
||||
getInterface: function SSLE_getInterface(aIID) {
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
QueryInterface: function SSLE_QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIBadCertListener2) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
/**
|
||||
To collect the SSL status we intercept the certificate error here
|
||||
and store the status for later use.
|
||||
*/
|
||||
notifyCertProblem: function SSLE_notifyCertProblem(socketInfo, sslStatus, targetHost) {
|
||||
this._sslStatus = sslStatus.QueryInterface(Ci.nsISSLStatus);
|
||||
return true; // suppress error UI
|
||||
},
|
||||
|
||||
/**
|
||||
Attempt to download the certificate for the location specified to get the SSLState
|
||||
for the certificate and the errors.
|
||||
*/
|
||||
_checkCert: function SSLE_checkCert(aURI) {
|
||||
this._sslStatus = null;
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
try {
|
||||
if(aURI) {
|
||||
req.open("GET", aURI.prePath, false);
|
||||
req.channel.notificationCallbacks = this;
|
||||
req.send(null);
|
||||
}
|
||||
} catch (e) {
|
||||
// We *expect* exceptions if there are problems with the certificate
|
||||
// presented by the site. Log it, just in case, but we can proceed here,
|
||||
// with appropriate sanity checks
|
||||
Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " +
|
||||
"This results in a (mostly harmless) exception being thrown. " +
|
||||
"Logged for information purposes only: " + e);
|
||||
}
|
||||
|
||||
return this._sslStatus;
|
||||
},
|
||||
|
||||
/**
|
||||
Internal method to create an override.
|
||||
*/
|
||||
_addOverride: function SSLE_addOverride(aURI, aWindow, temporary) {
|
||||
var SSLStatus = this._checkCert(aURI);
|
||||
var certificate = SSLStatus.serverCert;
|
||||
|
||||
var flags = 0;
|
||||
|
||||
// in private browsing do not store exceptions permanently ever
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
|
||||
temporary = true;
|
||||
}
|
||||
|
||||
if(SSLStatus.isUntrusted)
|
||||
flags |= this._overrideService.ERROR_UNTRUSTED;
|
||||
if(SSLStatus.isDomainMismatch)
|
||||
flags |= this._overrideService.ERROR_MISMATCH;
|
||||
if(SSLStatus.isNotValidAtThisTime)
|
||||
flags |= this._overrideService.ERROR_TIME;
|
||||
|
||||
this._overrideService.rememberValidityOverride(
|
||||
aURI.asciiHost,
|
||||
aURI.port,
|
||||
certificate,
|
||||
flags,
|
||||
temporary);
|
||||
},
|
||||
|
||||
/**
|
||||
Creates a permanent exception to override all overridable errors for
|
||||
the given URL.
|
||||
*/
|
||||
addPermanentException: function SSLE_addPermanentException(aURI, aWindow) {
|
||||
this._addOverride(aURI, aWindow, false);
|
||||
},
|
||||
|
||||
/**
|
||||
Creates a temporary exception to override all overridable errors for
|
||||
the given URL.
|
||||
*/
|
||||
addTemporaryException: function SSLE_addTemporaryException(aURI, aWindow) {
|
||||
this._addOverride(aURI, aWindow, true);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,82 @@
|
|||
/* 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/. */
|
||||
|
||||
var AlertsHelper = {
|
||||
_timeoutID: -1,
|
||||
_listener: null,
|
||||
_cookie: "",
|
||||
_clickable: false,
|
||||
get container() {
|
||||
delete this.container;
|
||||
let container = document.getElementById("alerts-container");
|
||||
|
||||
let self = this;
|
||||
container.addEventListener("transitionend", function() {
|
||||
self.alertTransitionOver();
|
||||
}, true);
|
||||
|
||||
return this.container = container;
|
||||
},
|
||||
|
||||
showAlertNotification: function ah_show(aImageURL, aTitle, aText, aTextClickable, aCookie, aListener) {
|
||||
this._clickable = aTextClickable || false;
|
||||
this._listener = aListener || null;
|
||||
this._cookie = aCookie || "";
|
||||
|
||||
// Reset the container settings from the last time so layout can happen naturally
|
||||
let container = this.container;
|
||||
container.removeAttribute("width");
|
||||
let alertText = document.getElementById("alerts-text");
|
||||
alertText.style.whiteSpace = "";
|
||||
|
||||
document.getElementById("alerts-image").setAttribute("src", aImageURL);
|
||||
document.getElementById("alerts-title").value = aTitle;
|
||||
alertText.textContent = aText;
|
||||
|
||||
container.hidden = false;
|
||||
let bcr = container.getBoundingClientRect();
|
||||
if (bcr.width > window.innerWidth - 50) {
|
||||
// If the window isn't wide enough, we need to re-layout
|
||||
container.setAttribute("width", window.innerWidth - 50); // force a max width
|
||||
alertText.style.whiteSpace = "pre-wrap"; // wrap text as needed
|
||||
bcr = container.getBoundingClientRect(); // recalculate the bcr
|
||||
}
|
||||
container.setAttribute("width", bcr.width); // redundant but cheap
|
||||
container.setAttribute("height", bcr.height);
|
||||
|
||||
container.classList.add("showing");
|
||||
|
||||
let timeout = Services.prefs.getIntPref("alerts.totalOpenTime");
|
||||
let self = this;
|
||||
if (this._timeoutID)
|
||||
clearTimeout(this._timeoutID);
|
||||
this._timeoutID = setTimeout(function() { self._timeoutAlert(); }, timeout);
|
||||
},
|
||||
|
||||
_timeoutAlert: function ah__timeoutAlert() {
|
||||
this._timeoutID = -1;
|
||||
|
||||
this.container.classList.remove("showing");
|
||||
if (this._listener)
|
||||
this._listener.observe(null, "alertfinished", this._cookie);
|
||||
},
|
||||
|
||||
alertTransitionOver: function ah_alertTransitionOver() {
|
||||
let container = this.container;
|
||||
if (!container.classList.contains("showing")) {
|
||||
container.height = 0;
|
||||
container.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
click: function ah_click(aEvent) {
|
||||
if (this._clickable && this._listener)
|
||||
this._listener.observe(null, "alertclickcallback", this._cookie);
|
||||
|
||||
if (this._timeoutID != -1) {
|
||||
clearTimeout(this._timeoutID);
|
||||
this._timeoutAlert();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,145 @@
|
|||
/* 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/. */
|
||||
|
||||
var CaptureDialog = {
|
||||
_video: null,
|
||||
_container: null,
|
||||
_lastOrientationEvent: null,
|
||||
|
||||
init: function() {
|
||||
document.getElementsByAttribute('command', 'cmd_ok')[0].focus();
|
||||
this._video = document.getElementById("capturepicker-video");
|
||||
this._container = document.getElementById("capturepicker-container");
|
||||
window.addEventListener("resize", this, false);
|
||||
window.addEventListener("deviceorientation", this, false);
|
||||
this.handleEvent({ type: "resize" });
|
||||
},
|
||||
|
||||
setPreviewOrientation: function(aEvent) {
|
||||
if (window.innerWidth < window.innerHeight) {
|
||||
if (aEvent.beta > 0)
|
||||
this._video.style.MozTransform = "rotate(90deg)";
|
||||
else
|
||||
this._video.style.MozTransform = "rotate(-90deg)";
|
||||
this._container.classList.add("vertical");
|
||||
}
|
||||
else {
|
||||
if (aEvent.gamma > 0)
|
||||
this._video.style.MozTransform = "rotate(180deg)";
|
||||
else
|
||||
this._video.style.MozTransform = "";
|
||||
this._container.classList.remove("vertical");
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
if (aEvent.type == "deviceorientation") {
|
||||
if (!this._lastOrientationEvent)
|
||||
this.setPreviewOrientation(aEvent);
|
||||
this._lastOrientationEvent = aEvent;
|
||||
}
|
||||
else if (aEvent.type == "resize") {
|
||||
if (this._lastOrientationEvent)
|
||||
this.setPreviewOrientation(this._lastOrientationEvent);
|
||||
}
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this.doClose(false);
|
||||
},
|
||||
|
||||
capture: function() {
|
||||
this.doClose(true);
|
||||
},
|
||||
|
||||
doClose: function(aResult) {
|
||||
window.removeEventListener("resize", this, false);
|
||||
window.removeEventListener("deviceorientation", this, false);
|
||||
let dialog = document.getElementById("capturepicker-dialog");
|
||||
dialog.arguments.result = aResult;
|
||||
if (aResult)
|
||||
dialog.arguments.path = this.saveFrame();
|
||||
document.getElementById("capturepicker-video").setAttribute("src", "");
|
||||
dialog.close();
|
||||
},
|
||||
|
||||
saveFrame: function() {
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let width = 320;
|
||||
let height = 240;
|
||||
let rotation = 0;
|
||||
|
||||
if (window.innerWidth < window.innerHeight) {
|
||||
width = 240;
|
||||
height = 320;
|
||||
if (this._lastOrientationEvent.beta > 0)
|
||||
rotation = Math.PI / 2;
|
||||
else
|
||||
rotation = -Math.PI / 2;
|
||||
}
|
||||
else {
|
||||
if (this._lastOrientationEvent.gamma > 0)
|
||||
rotation = Math.PI;
|
||||
}
|
||||
|
||||
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
//ctx.save();
|
||||
if (rotation != 0) {
|
||||
if (rotation == Math.PI / 2)
|
||||
ctx.translate(width, 0);
|
||||
else if (rotation == -Math.PI / 2)
|
||||
ctx.translate(-width, 0);
|
||||
else
|
||||
ctx.translate(width, height);
|
||||
ctx.rotate(rotation);
|
||||
}
|
||||
ctx.drawImage(document.getElementById("capturepicker-video"), 0, 0, 320, 240);
|
||||
//ctx.restore();
|
||||
let url = canvas.toDataURL("image/png");
|
||||
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
let file = tmpDir.clone();
|
||||
file.append("capture.png");
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
|
||||
|
||||
persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
|
||||
|
||||
let mDone = false;
|
||||
let mFailed = false;
|
||||
|
||||
let progressListener = {
|
||||
onProgressChange: function() {
|
||||
/* Ignore progress callback */
|
||||
},
|
||||
onStateChange: function(aProgress, aRequest, aStateFlag, aStatus) {
|
||||
if (aStateFlag & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
mDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
persist.progressListener = progressListener;
|
||||
|
||||
let source = Services.io.newURI(url, "UTF8", null);;
|
||||
persist.saveURI(source, null, null, null, null, file);
|
||||
|
||||
// don't wait more than 3 seconds for a successful save
|
||||
window.setTimeout(function() {
|
||||
mDone = true;
|
||||
mFailed = true;
|
||||
}, 3000);
|
||||
|
||||
while (!mDone)
|
||||
Services.tm.currentThread.processNextEvent(true);
|
||||
|
||||
return (mFailed ? null : file.path);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* 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/. */
|
||||
|
||||
/* appears to have something to do with capturing a camera image. tries to
|
||||
* create a dialog, which will fail. TBD. */
|
||||
var CapturePickerUI = {
|
||||
init: function() {
|
||||
this.messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
|
||||
this.messageManager.addMessageListener("CapturePicker:Show", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "CapturePicker:Show":
|
||||
let params = { result: true };
|
||||
let dialog = DialogUI.importModal(null, "chrome://browser/content/prompt/CaptureDialog.xul", params);
|
||||
document.getElementById("capturepicker-title").appendChild(document.createTextNode(aMessage.json.title));
|
||||
dialog.waitForClose();
|
||||
return { value: params.result, path: params.path };
|
||||
break;
|
||||
}
|
||||
// prevents warning from the script loader
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
/* 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/. */
|
||||
|
||||
var CharsetMenu = {
|
||||
_strings: null,
|
||||
_charsets: null,
|
||||
|
||||
get strings() {
|
||||
if (!this._strings)
|
||||
this._strings = Services.strings.createBundle("chrome://global/locale/charsetTitles.properties");
|
||||
return this._strings;
|
||||
},
|
||||
|
||||
init: function() {
|
||||
PageActions.register("pageaction-charset", this.updatePageAction, this);
|
||||
},
|
||||
|
||||
updatePageAction: function(aNode) {
|
||||
let pref = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data;
|
||||
if (pref == "true") {
|
||||
let charset = getBrowser().docShell.forcedCharset;
|
||||
if (charset) {
|
||||
charset = charset.toString();
|
||||
charset = charset.trim().toLowerCase();
|
||||
aNode.setAttribute("description", this.strings.GetStringFromName(charset + ".title"));
|
||||
} else if (aNode.hasAttribute("description")) {
|
||||
aNode.removeAttribute("description");
|
||||
}
|
||||
}
|
||||
return ("true" == pref)
|
||||
},
|
||||
|
||||
_toMenuItems: function(aCharsets, aCurrent) {
|
||||
let ret = [];
|
||||
aCharsets.forEach(function (aSet) {
|
||||
try {
|
||||
let string = aSet.trim().toLowerCase();
|
||||
ret.push({
|
||||
label: this.strings.GetStringFromName(string + ".title"),
|
||||
value: string,
|
||||
selected: (string == aCurrent)
|
||||
});
|
||||
} catch(ex) { }
|
||||
}, this);
|
||||
return ret;
|
||||
},
|
||||
|
||||
menu : {
|
||||
dispatchEvent: function(aEvent) {
|
||||
if (aEvent.type == "command")
|
||||
CharsetMenu.setCharset(this.menupopup.children[this.selectedIndex].value);
|
||||
},
|
||||
menupopup: {
|
||||
hasAttribute: function(aAttr) { return false; },
|
||||
},
|
||||
selectedIndex: -1
|
||||
},
|
||||
|
||||
get charsets() {
|
||||
if (!this._charsets) {
|
||||
this._charsets = Services.prefs.getComplexValue("intl.charsetmenu.browser.static", Ci.nsIPrefLocalizedString).data.split(",");
|
||||
}
|
||||
let charsets = this._charsets;
|
||||
let currentCharset = getBrowser().docShell.forcedCharset;
|
||||
|
||||
if (currentCharset) {
|
||||
currentCharset = currentCharset.toString();
|
||||
currentCharset = currentCharset.trim().toLowerCase();
|
||||
if (charsets.indexOf(currentCharset) == -1)
|
||||
charsets.splice(0, 0, currentCharset);
|
||||
}
|
||||
return this._toMenuItems(charsets, currentCharset);
|
||||
},
|
||||
|
||||
show: function showCharsetMenu() {
|
||||
this.menu.menupopup.children = this.charsets;
|
||||
MenuControlHelperUI.show(this.menu);
|
||||
},
|
||||
|
||||
setCharset: function setCharset(aCharset) {
|
||||
let browser = getBrowser();
|
||||
browser.messageManager.sendAsyncMessage("Browser:SetCharset", {
|
||||
charset: aCharset
|
||||
});
|
||||
let history = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService);
|
||||
history.setCharsetForURI(browser.documentURI, aCharset);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,173 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
var FindHelperUI = {
|
||||
type: "find",
|
||||
commands: {
|
||||
next: "cmd_findNext",
|
||||
previous: "cmd_findPrevious",
|
||||
close: "cmd_findClose"
|
||||
},
|
||||
|
||||
_open: false,
|
||||
_status: null,
|
||||
|
||||
get status() {
|
||||
return this._status;
|
||||
},
|
||||
|
||||
set status(val) {
|
||||
if (val != this._status) {
|
||||
this._status = val;
|
||||
if (!val)
|
||||
this._textbox.removeAttribute("status");
|
||||
else
|
||||
this._textbox.setAttribute("status", val);
|
||||
this.updateCommands(this._textbox.value);
|
||||
}
|
||||
},
|
||||
|
||||
init: function findHelperInit() {
|
||||
this._textbox = document.getElementById("find-helper-textbox");
|
||||
this._container = document.getElementById("content-navigator");
|
||||
|
||||
this._cmdPrevious = document.getElementById(this.commands.previous);
|
||||
this._cmdNext = document.getElementById(this.commands.next);
|
||||
|
||||
// Listen for find assistant messages from content
|
||||
messageManager.addMessageListener("FindAssist:Show", this);
|
||||
messageManager.addMessageListener("FindAssist:Hide", this);
|
||||
|
||||
// Listen for pan events happening on the browsers
|
||||
Elements.browsers.addEventListener("PanBegin", this, false);
|
||||
Elements.browsers.addEventListener("PanFinished", this, false);
|
||||
|
||||
// Listen for events where form assistant should be closed
|
||||
Elements.tabList.addEventListener("TabSelect", this, true);
|
||||
Elements.browsers.addEventListener("URLChanged", this, true);
|
||||
window.addEventListener("MozContextUIShow", this, true);
|
||||
window.addEventListener("MozContextUIExpand", this, true);
|
||||
},
|
||||
|
||||
receiveMessage: function findHelperReceiveMessage(aMessage) {
|
||||
let json = aMessage.json;
|
||||
switch(aMessage.name) {
|
||||
case "FindAssist:Show":
|
||||
ContextUI.dismiss();
|
||||
this.status = json.result;
|
||||
if (json.rect)
|
||||
this._zoom(Rect.fromRect(json.rect));
|
||||
break;
|
||||
|
||||
case "FindAssist:Hide":
|
||||
if (this._container.getAttribute("type") == this.type)
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function findHelperHandleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "MozContextUIShow":
|
||||
case "MozContextUIExpand":
|
||||
case "TabSelect":
|
||||
this.hide();
|
||||
break;
|
||||
|
||||
case "URLChanged":
|
||||
if (aEvent.detail && aEvent.target == getBrowser())
|
||||
this.hide();
|
||||
break;
|
||||
|
||||
case "PanBegin":
|
||||
this._container.style.visibility = "hidden";
|
||||
this._textbox.collapsed = true;
|
||||
break;
|
||||
|
||||
case "PanFinished":
|
||||
this._container.style.visibility = "visible";
|
||||
this._textbox.collapsed = false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
show: function findHelperShow() {
|
||||
|
||||
ContextUI.dismiss();
|
||||
|
||||
this._container.show(this);
|
||||
this.search(this._textbox.value);
|
||||
this._textbox.select();
|
||||
this._textbox.focus();
|
||||
this._open = true;
|
||||
|
||||
// Prevent the view to scroll automatically while searching
|
||||
Browser.selectedBrowser.scrollSync = false;
|
||||
},
|
||||
|
||||
hide: function findHelperHide() {
|
||||
if (!this._open)
|
||||
return;
|
||||
|
||||
this._textbox.value = "";
|
||||
this.status = null;
|
||||
this._textbox.blur();
|
||||
this._container.hide(this);
|
||||
this._open = false;
|
||||
|
||||
// Restore the scroll synchronisation
|
||||
Browser.selectedBrowser.scrollSync = true;
|
||||
},
|
||||
|
||||
goToPrevious: function findHelperGoToPrevious() {
|
||||
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Previous", { });
|
||||
},
|
||||
|
||||
goToNext: function findHelperGoToNext() {
|
||||
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Next", { });
|
||||
},
|
||||
|
||||
search: function findHelperSearch(aValue) {
|
||||
this.updateCommands(aValue);
|
||||
|
||||
// Don't bother searching if the value is empty
|
||||
if (aValue == "") {
|
||||
this.status = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Find", { searchString: aValue });
|
||||
},
|
||||
|
||||
updateCommands: function findHelperUpdateCommands(aValue) {
|
||||
let disabled = (this._status == Ci.nsITypeAheadFind.FIND_NOTFOUND) || (aValue == "");
|
||||
this._cmdPrevious.setAttribute("disabled", disabled);
|
||||
this._cmdNext.setAttribute("disabled", disabled);
|
||||
},
|
||||
|
||||
_zoom: function _findHelperZoom(aElementRect) {
|
||||
let autozoomEnabled = Services.prefs.getBoolPref("findhelper.autozoom");
|
||||
if (!aElementRect || !autozoomEnabled)
|
||||
return;
|
||||
|
||||
if (Browser.selectedTab.allowZoom) {
|
||||
let zoomLevel = Browser._getZoomLevelForRect(aElementRect);
|
||||
|
||||
// Clamp the zoom level relatively to the default zoom level of the page
|
||||
let defaultZoomLevel = Browser.selectedTab.getDefaultZoomLevel();
|
||||
zoomLevel = Util.clamp(zoomLevel, (defaultZoomLevel * kBrowserFormZoomLevelMin),
|
||||
(defaultZoomLevel * kBrowserFormZoomLevelMax));
|
||||
zoomLevel = Browser.selectedTab.clampZoomLevel(zoomLevel);
|
||||
|
||||
let zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, zoomLevel);
|
||||
AnimatedZoom.animateTo(zoomRect);
|
||||
} else {
|
||||
// Even if zooming is disabled we could need to reposition the view in
|
||||
// order to keep the element on-screen
|
||||
let zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, getBrowser().scale);
|
||||
AnimatedZoom.animateTo(zoomRect);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,454 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Responsible for filling in form information.
|
||||
* - When an element is focused, the browser view zooms in to the control.
|
||||
* - The caret positionning and the view are sync to keep the type
|
||||
* in text into view for input fields (text/textarea).
|
||||
* - Provides autocomplete box for input fields.
|
||||
*/
|
||||
|
||||
const kBrowserFormZoomLevelMin = 0.8;
|
||||
const kBrowserFormZoomLevelMax = 2.0;
|
||||
|
||||
// Prefs
|
||||
const kPrefFormHelperEnabled = "formhelper.enabled";
|
||||
const kPrefFormHelperMode = "formhelper.mode";
|
||||
const kPrefFormHelperZoom = "formhelper.autozoom";
|
||||
const kPrefFormHelperZoomCaret = "formhelper.autozoom.caret";
|
||||
|
||||
var FormHelperUI = {
|
||||
_debugEvents: false,
|
||||
_currentBrowser: null,
|
||||
_currentElement: null,
|
||||
_currentCaretRect: null,
|
||||
_currentElementRect: null,
|
||||
|
||||
type: "form",
|
||||
|
||||
get enabled() {
|
||||
return Services.prefs.getBoolPref(kPrefFormHelperEnabled);
|
||||
},
|
||||
|
||||
init: function formHelperInit() {
|
||||
// Listen for form assistant messages from content
|
||||
messageManager.addMessageListener("FormAssist:Show", this);
|
||||
messageManager.addMessageListener("FormAssist:Hide", this);
|
||||
messageManager.addMessageListener("FormAssist:Update", this);
|
||||
messageManager.addMessageListener("FormAssist:Resize", this);
|
||||
messageManager.addMessageListener("FormAssist:AutoComplete", this);
|
||||
messageManager.addMessageListener("FormAssist:ValidationMessage", this);
|
||||
|
||||
// Listen for events where form assistant should be closed or updated
|
||||
let tabs = Elements.tabList;
|
||||
tabs.addEventListener("TabSelect", this, true);
|
||||
tabs.addEventListener("TabClose", this, true);
|
||||
Elements.browsers.addEventListener("URLChanged", this, true);
|
||||
Elements.browsers.addEventListener("SizeChanged", this, true);
|
||||
|
||||
// Listen some events to show/hide arrows
|
||||
Elements.browsers.addEventListener("PanBegin", this, false);
|
||||
Elements.browsers.addEventListener("PanFinished", this, false);
|
||||
|
||||
// Dynamically enabled/disabled the form helper if needed depending on
|
||||
// the size of the screen
|
||||
let mode = Services.prefs.getIntPref(kPrefFormHelperMode);
|
||||
let state = (mode == 2) ? false : !!mode;
|
||||
Services.prefs.setBoolPref(kPrefFormHelperEnabled, state);
|
||||
},
|
||||
|
||||
/*
|
||||
* Open of the form helper proper. Currently doesn't display anything
|
||||
* on metro since the nav buttons are off.
|
||||
*/
|
||||
show: function formHelperShow(aElement, aHasPrevious, aHasNext) {
|
||||
// Delay the operation until all resize operations generated by the
|
||||
// keyboard apparition are done. This avoid doing unuseful zooming
|
||||
// operations.
|
||||
if (aElement.editable &&
|
||||
(MetroUtils.immersive && !ContentAreaObserver.isKeyboardOpened &&
|
||||
!InputSourceHelper.isPrecise)) {
|
||||
this._waitForKeyboard(aElement, aHasPrevious, aHasNext);
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentBrowser = Browser.selectedBrowser;
|
||||
this._currentCaretRect = null;
|
||||
|
||||
let lastElement = this._currentElement || null;
|
||||
|
||||
this._currentElement = {
|
||||
id: aElement.id,
|
||||
name: aElement.name,
|
||||
title: aElement.title,
|
||||
value: aElement.value,
|
||||
maxLength: aElement.maxLength,
|
||||
type: aElement.type,
|
||||
choices: aElement.choices,
|
||||
isAutocomplete: aElement.isAutocomplete,
|
||||
validationMessage: aElement.validationMessage,
|
||||
list: aElement.list,
|
||||
rect: aElement.rect
|
||||
};
|
||||
|
||||
this._zoom(Rect.fromRect(aElement.rect), Rect.fromRect(aElement.caretRect));
|
||||
this._updateContainerForSelect(lastElement, this._currentElement);
|
||||
this._updatePopupsFor(this._currentElement);
|
||||
|
||||
// Prevent the view to scroll automatically while typing
|
||||
this._currentBrowser.scrollSync = false;
|
||||
},
|
||||
|
||||
hide: function formHelperHide() {
|
||||
SelectHelperUI.hide();
|
||||
AutofillMenuUI.hide();
|
||||
|
||||
// Restore the scroll synchonisation
|
||||
if (this._currentBrowser)
|
||||
this._currentBrowser.scrollSync = true;
|
||||
|
||||
// reset current Element and Caret Rect
|
||||
this._currentElementRect = null;
|
||||
this._currentCaretRect = null;
|
||||
|
||||
this._updateContainerForSelect(this._currentElement, null);
|
||||
if (this._currentBrowser)
|
||||
this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { });
|
||||
},
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
handleEvent: function formHelperHandleEvent(aEvent) {
|
||||
if (this._debugEvents) Util.dumpLn(aEvent.type);
|
||||
|
||||
if (!this._open)
|
||||
return;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "TabSelect":
|
||||
case "TabClose":
|
||||
case "PanBegin":
|
||||
case "SizeChanged":
|
||||
this.hide();
|
||||
break;
|
||||
|
||||
case "URLChanged":
|
||||
if (aEvent.detail && aEvent.target == getBrowser())
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function formHelperReceiveMessage(aMessage) {
|
||||
if (this._debugEvents) Util.dumpLn(aMessage.name);
|
||||
let allowedMessages = ["FormAssist:Show", "FormAssist:Hide",
|
||||
"FormAssist:AutoComplete", "FormAssist:ValidationMessage"];
|
||||
if (!this._open && allowedMessages.indexOf(aMessage.name) == -1)
|
||||
return;
|
||||
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "FormAssist:Show":
|
||||
// if the user has manually disabled the Form Assistant UI we still
|
||||
// want to show a UI for <select /> element and still want to show
|
||||
// autocomplete suggestions but not managed by FormHelperUI
|
||||
if (this.enabled) {
|
||||
this.show(json.current, json.hasPrevious, json.hasNext)
|
||||
} else if (json.current.choices) {
|
||||
SelectHelperUI.show(json.current.choices, json.current.title, json.current.rect);
|
||||
} else {
|
||||
this._currentElementRect = Rect.fromRect(json.current.rect);
|
||||
this._currentBrowser = getBrowser();
|
||||
this._updatePopupsFor(json.current);
|
||||
}
|
||||
break;
|
||||
|
||||
case "FormAssist:Hide":
|
||||
if (this.enabled) {
|
||||
this.hide();
|
||||
}
|
||||
break;
|
||||
|
||||
case "FormAssist:Resize":
|
||||
if (!ContentAreaObserver.isKeyboardOpened)
|
||||
return;
|
||||
|
||||
let element = json.current;
|
||||
this._zoom(Rect.fromRect(element.rect), Rect.fromRect(element.caretRect));
|
||||
break;
|
||||
|
||||
case "FormAssist:ValidationMessage":
|
||||
this._updatePopupsFor(json.current, { fromInput: true });
|
||||
break;
|
||||
|
||||
case "FormAssist:AutoComplete":
|
||||
this._updatePopupsFor(json.current, { fromInput: true });
|
||||
break;
|
||||
|
||||
case "FormAssist:Update":
|
||||
if (!ContentAreaObserver.isKeyboardOpened)
|
||||
return;
|
||||
//this._zoom(null, Rect.fromRect(json.caretRect));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
doAutoComplete: function formHelperDoAutoComplete(aData) {
|
||||
this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:AutoComplete",
|
||||
{ value: aData });
|
||||
},
|
||||
|
||||
get _open() {
|
||||
// XXX we don't have the ability to test zooming
|
||||
return true;
|
||||
},
|
||||
|
||||
/*
|
||||
* Update all popups based on the type of form element we are
|
||||
* dealing with.
|
||||
*/
|
||||
_updatePopupsFor: function _formHelperUpdatePopupsFor(aElement, options) {
|
||||
options = options || {};
|
||||
|
||||
let fromInput = 'fromInput' in options && options.fromInput;
|
||||
|
||||
// The order of the updates matters here. If the popup update was
|
||||
// triggered from user input (e.g. key press in an input element),
|
||||
// we first check if there are input suggestions then check for
|
||||
// a validation message. The idea here is that the validation message
|
||||
// will be shown straight away once the invalid element is focused
|
||||
// and suggestions will be shown as user inputs data. Only one popup
|
||||
// is shown at a time. If both are not shown, then we ensure any
|
||||
// previous popups are hidden.
|
||||
let noPopupsShown = fromInput ?
|
||||
(!this._updateSuggestionsFor(aElement) &&
|
||||
!this._updateFormValidationFor(aElement)) :
|
||||
(!this._updateFormValidationFor(aElement) &&
|
||||
!this._updateSuggestionsFor(aElement));
|
||||
|
||||
if (noPopupsShown) {
|
||||
AutofillMenuUI.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Populates the autofill menu for this element.
|
||||
*/
|
||||
_updateSuggestionsFor: function _formHelperUpdateSuggestionsFor(aElement) {
|
||||
let suggestions = this._getAutocompleteSuggestions(aElement);
|
||||
if (!suggestions.length)
|
||||
return false;
|
||||
|
||||
// the scrollX/scrollY position can change because of the animated zoom so
|
||||
// delay the suggestions positioning
|
||||
/*
|
||||
if (AnimatedZoom.isZooming()) {
|
||||
let self = this;
|
||||
this._waitForZoom(function() {
|
||||
self._updateSuggestionsFor(aElement);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
AutofillMenuUI.show(this._currentElementRect, suggestions);
|
||||
return true;
|
||||
},
|
||||
|
||||
_updateFormValidationFor: function _formHelperUpdateFormValidationFor(aElement) {
|
||||
if (!aElement.validationMessage)
|
||||
return false;
|
||||
/*
|
||||
// the scrollX/scrollY position can change because of the animated zoom so
|
||||
// delay the suggestions positioning
|
||||
if (AnimatedZoom.isZooming()) {
|
||||
let self = this;
|
||||
this._waitForZoom(function() {
|
||||
self._updateFormValidationFor(aElement);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
let validationContainer = document.getElementById("form-helper-validation-container");
|
||||
|
||||
// Update label with form validation message
|
||||
validationContainer.firstChild.value = aElement.validationMessage;
|
||||
|
||||
ContentPopupHelper.popup = validationContainer;
|
||||
ContentPopupHelper.anchorTo(this._currentElementRect);
|
||||
*/
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Retrieve the autocomplete list from the autocomplete service for an element
|
||||
*/
|
||||
_getAutocompleteSuggestions: function _formHelperGetAutocompleteSuggestions(aElement) {
|
||||
if (!aElement.isAutocomplete) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let suggestions = [];
|
||||
|
||||
let autocompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete);
|
||||
let results = autocompleteService.autoCompleteSearch(aElement.name || aElement.id, aElement.value, aElement, null);
|
||||
if (results.matchCount > 0) {
|
||||
for (let i = 0; i < results.matchCount; i++) {
|
||||
let value = results.getValueAt(i);
|
||||
|
||||
// Do not show the value if it is the current one in the input field
|
||||
if (value == aElement.value)
|
||||
continue;
|
||||
|
||||
suggestions.push({ "label": value, "value": value});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the datalist elements provided by the website, note that the
|
||||
// displayed value can differ from the real value of the element.
|
||||
let options = aElement.list;
|
||||
for (let i = 0; i < options.length; i++)
|
||||
suggestions.push(options[i]);
|
||||
|
||||
return suggestions;
|
||||
},
|
||||
|
||||
/*
|
||||
* Setup for displaying the selection choices menu
|
||||
*/
|
||||
_updateContainerForSelect: function _formHelperUpdateContainerForSelect(aLastElement, aCurrentElement) {
|
||||
let lastHasChoices = aLastElement && (aLastElement.choices != null);
|
||||
let currentHasChoices = aCurrentElement && (aCurrentElement.choices != null);
|
||||
|
||||
if (currentHasChoices)
|
||||
SelectHelperUI.show(aCurrentElement.choices, aCurrentElement.title, aCurrentElement.rect);
|
||||
else if (lastHasChoices)
|
||||
SelectHelperUI.hide();
|
||||
},
|
||||
|
||||
/*
|
||||
* Zoom and move viewport so that element is legible and touchable.
|
||||
*/
|
||||
_zoom: function _formHelperZoom(aElementRect, aCaretRect) {
|
||||
let browser = getBrowser();
|
||||
let zoomRect = Rect.fromRect(browser.getBoundingClientRect());
|
||||
|
||||
this._currentElementRect = aElementRect;
|
||||
|
||||
// Zoom to a specified Rect
|
||||
let autozoomEnabled = Services.prefs.getBoolPref(kPrefFormHelperZoom);
|
||||
if (aElementRect && Browser.selectedTab.allowZoom && autozoomEnabled) {
|
||||
this._currentElementRect = aElementRect;
|
||||
|
||||
// Zoom to an element by keeping the caret into view
|
||||
let zoomLevel = Browser.selectedTab.clampZoomLevel(this._getZoomLevelForRect(aElementRect));
|
||||
|
||||
zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, zoomLevel);
|
||||
AnimatedZoom.animateTo(zoomRect);
|
||||
} else if (aElementRect && !Browser.selectedTab.allowZoom && autozoomEnabled) {
|
||||
this._currentElementRect = aElementRect;
|
||||
|
||||
// Even if zooming is disabled we could need to reposition the view in
|
||||
// order to keep the element on-screen
|
||||
zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, browser.scale);
|
||||
AnimatedZoom.animateTo(zoomRect);
|
||||
}
|
||||
|
||||
this._ensureCaretVisible(aCaretRect);
|
||||
},
|
||||
|
||||
_ensureCaretVisible: function _ensureCaretVisible(aCaretRect) {
|
||||
if (!aCaretRect || !Services.prefs.getBoolPref(kPrefFormHelperZoomCaret))
|
||||
return;
|
||||
|
||||
// the scrollX/scrollY position can change because of the animated zoom so
|
||||
// delay the caret adjustment
|
||||
if (AnimatedZoom.isZooming()) {
|
||||
let self = this;
|
||||
this._waitForZoom(function() {
|
||||
self._ensureCaretVisible(aCaretRect);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = getBrowser();
|
||||
let zoomRect = Rect.fromRect(browser.getBoundingClientRect());
|
||||
|
||||
this._currentCaretRect = aCaretRect;
|
||||
let caretRect = aCaretRect.clone().scale(browser.scale, browser.scale);
|
||||
|
||||
let scroll = browser.getRootView().getPosition();
|
||||
zoomRect = new Rect(scroll.x, scroll.y, zoomRect.width, zoomRect.height);
|
||||
if (zoomRect.contains(caretRect))
|
||||
return;
|
||||
|
||||
let [deltaX, deltaY] = this._getOffsetForCaret(caretRect, zoomRect);
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
let view = browser.getRootView();
|
||||
view.scrollBy(deltaX, deltaY);
|
||||
}
|
||||
},
|
||||
|
||||
_waitForZoom: function _formHelperWaitForZoom(aCallback) {
|
||||
let currentElement = this._currentElement;
|
||||
let self = this;
|
||||
window.addEventListener("AnimatedZoomEnd", function() {
|
||||
window.removeEventListener("AnimatedZoomEnd", arguments.callee, true);
|
||||
// Ensure the current element has not changed during this interval
|
||||
if (self._currentElement != currentElement)
|
||||
return;
|
||||
|
||||
aCallback();
|
||||
}, true);
|
||||
},
|
||||
|
||||
_getZoomLevelForRect: function _getZoomLevelForRect(aRect) {
|
||||
const margin = 30;
|
||||
let zoomLevel = getBrowser().getBoundingClientRect().width / (aRect.width + margin);
|
||||
|
||||
// Clamp the zoom level relatively to the default zoom level of the page
|
||||
let defaultZoomLevel = Browser.selectedTab.getDefaultZoomLevel();
|
||||
return Util.clamp(zoomLevel, (defaultZoomLevel * kBrowserFormZoomLevelMin),
|
||||
(defaultZoomLevel * kBrowserFormZoomLevelMax));
|
||||
},
|
||||
|
||||
_getOffsetForCaret: function _formHelperGetOffsetForCaret(aCaretRect, aRect) {
|
||||
// Determine if we need to move left or right to bring the caret into view
|
||||
let deltaX = 0;
|
||||
if (aCaretRect.right > aRect.right)
|
||||
deltaX = aCaretRect.right - aRect.right;
|
||||
if (aCaretRect.left < aRect.left)
|
||||
deltaX = aCaretRect.left - aRect.left;
|
||||
|
||||
// Determine if we need to move up or down to bring the caret into view
|
||||
let deltaY = 0;
|
||||
if (aCaretRect.bottom > aRect.bottom)
|
||||
deltaY = aCaretRect.bottom - aRect.bottom;
|
||||
if (aCaretRect.top < aRect.top)
|
||||
deltaY = aCaretRect.top - aRect.top;
|
||||
|
||||
return [deltaX, deltaY];
|
||||
},
|
||||
|
||||
_waitForKeyboard: function formHelperWaitForKeyboard(aElement, aHasPrevious, aHasNext) {
|
||||
let self = this;
|
||||
window.addEventListener("KeyboardChanged", function(aEvent) {
|
||||
window.removeEventListener("KeyboardChanged", arguments.callee, false);
|
||||
|
||||
if (AnimatedZoom.isZooming()) {
|
||||
self._waitForZoom(function() {
|
||||
self.show(aElement, aHasPrevious, aHasNext);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
self.show(aElement, aHasPrevious, aHasNext);
|
||||
}, false);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Utility class to handle manipulations of the identity indicators in the UI
|
||||
*/
|
||||
|
||||
var IdentityUI = {
|
||||
// Mode strings used to control CSS display
|
||||
IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
|
||||
IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
|
||||
IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
|
||||
|
||||
// Cache the most recent SSLStatus and Location seen in checkIdentity
|
||||
_lastStatus : null,
|
||||
_lastLocation : null,
|
||||
|
||||
/**
|
||||
* Build out a cache of the elements that we need frequently.
|
||||
*/
|
||||
_cacheElements: function() {
|
||||
this._identityBox = document.getElementById("identity-box");
|
||||
this._identityPopup = document.getElementById("identity-container");
|
||||
this._identityPopupContentBox = document.getElementById("identity-popup-content-box");
|
||||
this._identityPopupContentHost = document.getElementById("identity-popup-content-host");
|
||||
this._identityPopupContentOwner = document.getElementById("identity-popup-content-owner");
|
||||
this._identityPopupContentSupp = document.getElementById("identity-popup-content-supplemental");
|
||||
this._identityPopupContentVerif = document.getElementById("identity-popup-content-verifier");
|
||||
this._identityPopupEncLabel = document.getElementById("identity-popup-encryption-label");
|
||||
this._identityIcon = document.getElementById("identity-icon");
|
||||
this._identityIconLabel = document.getElementById("identity-icon-label");
|
||||
this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
|
||||
},
|
||||
|
||||
init: function init() {
|
||||
this._staticStrings = {};
|
||||
this._staticStrings[this.IDENTITY_MODE_DOMAIN_VERIFIED] = {
|
||||
encryption_label: Strings.browser.GetStringFromName("identity.encrypted2")
|
||||
};
|
||||
this._staticStrings[this.IDENTITY_MODE_IDENTIFIED] = {
|
||||
encryption_label: Strings.browser.GetStringFromName("identity.encrypted2")
|
||||
};
|
||||
this._staticStrings[this.IDENTITY_MODE_UNKNOWN] = {
|
||||
encryption_label: Strings.browser.GetStringFromName("identity.unencrypted2")
|
||||
};
|
||||
|
||||
// Close the popup when reloading the page
|
||||
Elements.browsers.addEventListener("URLChanged", this, true);
|
||||
|
||||
this._cacheElements();
|
||||
},
|
||||
|
||||
getIdentityData: function() {
|
||||
return this._lastStatus.serverCert;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the identity of the page being displayed by examining its SSL cert
|
||||
* (if available) and, if necessary, update the UI to reflect this.
|
||||
*/
|
||||
checkIdentity: function() {
|
||||
let browser = getBrowser();
|
||||
let state = browser.securityUI.state;
|
||||
let location = browser.currentURI;
|
||||
let currentStatus = browser.securityUI.SSLStatus;
|
||||
|
||||
this._lastStatus = currentStatus;
|
||||
this._lastLocation = {};
|
||||
|
||||
try {
|
||||
// make a copy of the passed in location to avoid cycles
|
||||
this._lastLocation = { host: location.hostPort, hostname: location.host, port: location.port == -1 ? "" : location.port};
|
||||
} catch(e) { }
|
||||
|
||||
if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
|
||||
this.setMode(this.IDENTITY_MODE_IDENTIFIED);
|
||||
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
|
||||
this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
|
||||
else
|
||||
this.setMode(this.IDENTITY_MODE_UNKNOWN);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the eTLD+1 version of the current hostname
|
||||
*/
|
||||
getEffectiveHost: function() {
|
||||
// Cache the eTLDService if this is our first time through
|
||||
if (!this._eTLDService)
|
||||
this._eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]
|
||||
.getService(Ci.nsIEffectiveTLDService);
|
||||
try {
|
||||
return this._eTLDService.getBaseDomainFromHost(this._lastLocation.hostname);
|
||||
} catch (e) {
|
||||
// If something goes wrong (e.g. hostname is an IP address) just fail back
|
||||
// to the full domain.
|
||||
return this._lastLocation.hostname;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the UI to reflect the specified mode, which should be one of the
|
||||
* IDENTITY_MODE_* constants.
|
||||
*/
|
||||
setMode: function(newMode) {
|
||||
this._identityBox.setAttribute("mode", newMode);
|
||||
this.setIdentityMessages(newMode);
|
||||
|
||||
// Update the popup too, if it's open
|
||||
if (!this._identityPopup.hidden)
|
||||
this.setPopupMessages(newMode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the messages for the primary identity UI based on the specified mode,
|
||||
* and the details of the SSL cert, where applicable
|
||||
*
|
||||
* @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
|
||||
*/
|
||||
setIdentityMessages: function(newMode) {
|
||||
let strings = Strings.browser;
|
||||
let tooltip = "";
|
||||
let icon_country_label = "";
|
||||
let icon_labels_dir = "ltr";
|
||||
let icon_label = "";
|
||||
|
||||
if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
|
||||
var iData = this.getIdentityData();
|
||||
|
||||
// We need a port number for all lookups. If one hasn't been specified, use
|
||||
// the https default
|
||||
var lookupHost = this._lastLocation.host;
|
||||
if (lookupHost.indexOf(':') < 0)
|
||||
lookupHost += ":443";
|
||||
|
||||
// Cache the override service the first time we need to check it
|
||||
if (!this._overrideService)
|
||||
this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService);
|
||||
|
||||
// Verifier is either the CA Org, for a normal cert, or a special string
|
||||
// for certs that are trusted because of a security exception.
|
||||
tooltip = strings.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1);
|
||||
|
||||
// Check whether this site is a security exception.
|
||||
if (iData.isException)
|
||||
tooltip = strings.GetStringFromName("identity.identified.verified_by_you");
|
||||
}
|
||||
else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
|
||||
// If it's identified, then we can populate the dialog with credentials
|
||||
iData = this.getIdentityData();
|
||||
tooltip = strings.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1);
|
||||
|
||||
icon_label = iData.subjectOrg;
|
||||
if (iData.country)
|
||||
icon_country_label = "(" + iData.country + ")";
|
||||
|
||||
// If the organization name starts with an RTL character, then
|
||||
// swap the positions of the organization and country code labels.
|
||||
// The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
|
||||
// macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
|
||||
// fixed, this test should be replaced by one adhering to the
|
||||
// Unicode Bidirectional Algorithm proper (at the paragraph level).
|
||||
icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
|
||||
"rtl" : "ltr";
|
||||
}
|
||||
else {
|
||||
tooltip = strings.GetStringFromName("identity.unknown.tooltip");
|
||||
}
|
||||
|
||||
// Push the appropriate strings out to the UI
|
||||
this._identityBox.tooltipText = tooltip;
|
||||
this._identityIconLabel.value = icon_label;
|
||||
this._identityIconCountryLabel.value = icon_country_label;
|
||||
// Set cropping and direction
|
||||
this._identityIconLabel.crop = icon_country_label ? "end" : "center";
|
||||
this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
|
||||
// Hide completely if the organization label is empty
|
||||
this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the title and content messages for the identity message popup,
|
||||
* based on the specified mode, and the details of the SSL cert, where
|
||||
* applicable
|
||||
*
|
||||
* @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
|
||||
*/
|
||||
setPopupMessages: function(newMode) {
|
||||
this._identityPopup.setAttribute("mode", newMode);
|
||||
this._identityPopupContentBox.className = newMode;
|
||||
|
||||
// Set the static strings up front
|
||||
this._identityPopupEncLabel.textContent = this._staticStrings[newMode].encryption_label;
|
||||
|
||||
// Initialize the optional strings to empty values
|
||||
var supplemental = "";
|
||||
var verifier = "";
|
||||
|
||||
if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
|
||||
var iData = this.getIdentityData();
|
||||
var host = this.getEffectiveHost();
|
||||
var owner = Strings.browser.GetStringFromName("identity.ownerUnknown2");
|
||||
verifier = this._identityBox.tooltipText;
|
||||
supplemental = "";
|
||||
}
|
||||
else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
|
||||
// If it's identified, then we can populate the dialog with credentials
|
||||
iData = this.getIdentityData();
|
||||
host = this.getEffectiveHost();
|
||||
owner = iData.subjectOrg;
|
||||
verifier = this._identityBox.tooltipText;
|
||||
|
||||
// Build an appropriate supplemental block out of whatever location data we have
|
||||
if (iData.city)
|
||||
supplemental += iData.city + "\n";
|
||||
if (iData.state && iData.country)
|
||||
supplemental += Strings.browser.formatStringFromName("identity.identified.state_and_country", [iData.state, iData.country], 2);
|
||||
else if (iData.state) // State only
|
||||
supplemental += iData.state;
|
||||
else if (iData.country) // Country only
|
||||
supplemental += iData.country;
|
||||
} else {
|
||||
// These strings will be hidden in CSS anyhow
|
||||
host = "";
|
||||
owner = "";
|
||||
}
|
||||
|
||||
// Push the appropriate strings out to the UI
|
||||
this._identityPopupContentHost.textContent = host;
|
||||
this._identityPopupContentOwner.textContent = owner;
|
||||
this._identityPopupContentSupp.textContent = supplemental;
|
||||
this._identityPopupContentVerif.textContent = verifier;
|
||||
|
||||
PageActions.updateSiteMenu();
|
||||
},
|
||||
|
||||
show: function ih_show() {
|
||||
BrowserUI.showContent();
|
||||
|
||||
Elements.contentShowing.setAttribute("disabled", "true");
|
||||
|
||||
// Update the popup strings
|
||||
this.setPopupMessages(this._identityBox.getAttribute("mode") || this.IDENTITY_MODE_UNKNOWN);
|
||||
|
||||
this._identityPopup.hidden = false;
|
||||
this._identityBox.setAttribute("open", "true");
|
||||
|
||||
DialogUI.pushPopup(this, [this._identityPopup, this._identityBox, Elements.tray]);
|
||||
this._identityPopup.anchorTo(this._identityBox, "after_start");
|
||||
},
|
||||
|
||||
hide: function ih_hide() {
|
||||
Elements.contentShowing.setAttribute("disabled", "false");
|
||||
|
||||
this._identityPopup.hidden = true;
|
||||
this._identityBox.removeAttribute("open");
|
||||
|
||||
DialogUI.popPopup(this);
|
||||
},
|
||||
|
||||
toggle: function ih_toggle() {
|
||||
if (this._identityPopup.hidden)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Click handler for the identity-box element in primary chrome.
|
||||
*/
|
||||
handleIdentityButtonEvent: function(aEvent) {
|
||||
aEvent.stopPropagation();
|
||||
|
||||
if ((aEvent.type == "click" && aEvent.button != 0) ||
|
||||
(aEvent.type == "keypress" && aEvent.charCode != KeyEvent.DOM_VK_SPACE &&
|
||||
aEvent.keyCode != KeyEvent.DOM_VK_RETURN))
|
||||
return; // Left click, space or enter only
|
||||
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
if (aEvent.type == "URLChanged" && aEvent.target == Browser.selectedBrowser && !this._identityPopup.hidden)
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Helper class for IndexedDB, parent part. Listens to
|
||||
* messages from the child and shows prompts for them.
|
||||
*/
|
||||
let IndexedDB = {
|
||||
_permissionsPrompt: "indexedDB-permissions-prompt",
|
||||
_permissionsResponse: "indexedDB-permissions-response",
|
||||
|
||||
_quotaPrompt: "indexedDB-quota-prompt",
|
||||
_quotaResponse: "indexedDB-quota-response",
|
||||
_quotaCancel: "indexedDB-quota-cancel",
|
||||
|
||||
_notificationIcon: "indexedDB-notification-icon",
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "IndexedDB:Prompt":
|
||||
this.showPrompt(aMessage);
|
||||
}
|
||||
},
|
||||
|
||||
showPrompt: function(aMessage) {
|
||||
let browser = aMessage.target;
|
||||
let payload = aMessage.json;
|
||||
let host = payload.host;
|
||||
let topic = payload.topic;
|
||||
let type;
|
||||
|
||||
if (topic == this._permissionsPrompt) {
|
||||
type = "indexedDB";
|
||||
payload.responseTopic = this._permissionsResponse;
|
||||
} else if (topic == this._quotaPrompt) {
|
||||
type = "indexedDBQuota";
|
||||
payload.responseTopic = this._quotaResponse;
|
||||
} else if (topic == this._quotaCancel) {
|
||||
payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
|
||||
// XXX Need to actually save this?
|
||||
return;
|
||||
}
|
||||
|
||||
let prompt = Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt);
|
||||
|
||||
// If the user waits a long time before responding, we default to UNKNOWN_ACTION.
|
||||
let timeoutId = setTimeout(function() {
|
||||
payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
|
||||
timeoutId = null;
|
||||
}, 30000);
|
||||
|
||||
function checkTimeout() {
|
||||
if (timeoutId === null) return true;
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
prompt.prompt({
|
||||
type: type,
|
||||
uri: Services.io.newURI(payload.location, null, null),
|
||||
window: null,
|
||||
element: aMessage.target,
|
||||
|
||||
cancel: function() {
|
||||
if (checkTimeout()) return;
|
||||
payload.permission = Ci.nsIPermissionManager.DENY_ACTION;
|
||||
browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
|
||||
},
|
||||
|
||||
allow: function() {
|
||||
if (checkTimeout()) return;
|
||||
payload.permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/* 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/. */
|
||||
|
||||
var MasterPasswordUI = {
|
||||
_dialog: null,
|
||||
_tokenName: "",
|
||||
|
||||
get _secModuleDB() {
|
||||
delete this._secModuleDB;
|
||||
return this._secModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(Ci.nsIPKCS11ModuleDB);
|
||||
},
|
||||
|
||||
get _pk11DB() {
|
||||
delete this._pk11DB;
|
||||
return this._pk11DB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(Ci.nsIPK11TokenDB);
|
||||
},
|
||||
|
||||
_setPassword: function _setPassword(password) {
|
||||
try {
|
||||
let status;
|
||||
let slot = this._secModuleDB.findSlotByName(this._tokenName);
|
||||
if (slot)
|
||||
status = slot.status;
|
||||
else
|
||||
return false;
|
||||
|
||||
let token = this._pk11DB.findTokenByName(this._tokenName);
|
||||
|
||||
if (status == Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED) {
|
||||
token.initPassword(password);
|
||||
} else if (status == Ci.nsIPKCS11Slot.SLOT_READY) {
|
||||
token.changePassword("", password);
|
||||
}
|
||||
return true;
|
||||
} catch(e) {
|
||||
dump("--- MasterPasswordUI._setPassword exception: " + e + "\n");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_removePassword: function _removePassword(password) {
|
||||
try {
|
||||
let token = this._pk11DB.getInternalKeyToken();
|
||||
if (token.checkPassword(password)) {
|
||||
token.changePassword(password, "");
|
||||
return true;
|
||||
}
|
||||
} catch(e) {
|
||||
dump("--- MasterPasswordUI._removePassword exception: " + e + "\n");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
show: function mp_show(aSet) {
|
||||
let dialogId = aSet ? "masterpassword-change" : "masterpassword-remove";
|
||||
if (document.getElementById(dialogId))
|
||||
return;
|
||||
|
||||
let dialog = aSet ? "chrome://browser/content/prompt/masterPassword.xul"
|
||||
: "chrome://browser/content/prompt/removeMasterPassword.xul";
|
||||
this._dialog = DialogUI.importModal(window, dialog, null);
|
||||
DialogUI.pushPopup(this, this._dialog);
|
||||
|
||||
if (aSet) {
|
||||
this.checkPassword();
|
||||
document.getElementById("masterpassword-newpassword1").focus();
|
||||
} else {
|
||||
document.getElementById("masterpassword-oldpassword").focus();
|
||||
}
|
||||
},
|
||||
|
||||
hide: function mp_hide(aValue) {
|
||||
this.updatePreference();
|
||||
this._dialog.close();
|
||||
this._dialog = null;
|
||||
DialogUI.popPopup(this);
|
||||
},
|
||||
|
||||
setPassword: function mp_setPassword() {
|
||||
if (!this.checkPassword())
|
||||
return;
|
||||
|
||||
let newPasswordValue = document.getElementById("masterpassword-newpassword1").value;
|
||||
if (this._setPassword(newPasswordValue)) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
removePassword: function mp_removePassword() {
|
||||
let oldPassword = document.getElementById("masterpassword-oldpassword").value;
|
||||
if (this._removePassword(oldPassword)) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
checkPassword: function mp_checkPassword() {
|
||||
let newPasswordValue1 = document.getElementById("masterpassword-newpassword1").value;
|
||||
let newPasswordValue2 = document.getElementById("masterpassword-newpassword2").value;
|
||||
|
||||
let buttonOk = this._dialog.getElementsByAttribute("class", "prompt-buttons")[0].firstChild;
|
||||
let isPasswordValid = this._secModuleDB.isFIPSEnabled ? (newPasswordValue1 != "" && newPasswordValue1 == newPasswordValue2)
|
||||
: (newPasswordValue1 == newPasswordValue2);
|
||||
buttonOk.setAttribute("disabled", !isPasswordValid);
|
||||
|
||||
return isPasswordValid;
|
||||
},
|
||||
|
||||
checkOldPassword: function mp_checkOldPassword() {
|
||||
let oldPassword = document.getElementById("masterpassword-oldpassword");
|
||||
|
||||
let buttonOk = this._dialog.getElementsByAttribute("class", "prompt-buttons")[0].firstChild;
|
||||
let isPasswordValid = this._pk11DB.getInternalKeyToken().checkPassword(oldPassword.value);
|
||||
buttonOk.setAttribute("disabled", !isPasswordValid);
|
||||
},
|
||||
|
||||
hasMasterPassword: function mp_hasMasterPassword() {
|
||||
let slot = this._secModuleDB.findSlotByName(this._tokenName);
|
||||
if (slot) {
|
||||
let status = slot.status;
|
||||
return status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED && status != Ci.nsIPKCS11Slot.SLOT_READY;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
updatePreference: function mp_updatePreference() {
|
||||
document.getElementById("prefs-master-password").value = this.hasMasterPassword();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,438 @@
|
|||
/* 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/. */
|
||||
|
||||
// Positioning buffer enforced between the edge of a context menu
|
||||
// and the edge of the screen.
|
||||
const kPositionPadding = 10;
|
||||
|
||||
var AutofillMenuUI = {
|
||||
_popupState: null,
|
||||
__menuPopup: null,
|
||||
|
||||
get _panel() { return document.getElementById("autofill-container"); },
|
||||
get _popup() { return document.getElementById("autofill-popup"); },
|
||||
get _commands() { return this._popup.childNodes[0]; },
|
||||
|
||||
get _menuPopup() {
|
||||
if (!this.__menuPopup)
|
||||
this.__menuPopup = new MenuPopup(this._panel, this._popup);
|
||||
this.__menuPopup._wantTypeBehind = true;
|
||||
|
||||
return this.__menuPopup;
|
||||
},
|
||||
|
||||
_firePopupEvent: function _firePopupEvent(aEventName) {
|
||||
let menupopup = this._currentControl.menupopup;
|
||||
if (menupopup.hasAttribute(aEventName)) {
|
||||
let func = new Function("event", menupopup.getAttribute(aEventName));
|
||||
func.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
_emptyCommands: function _emptyCommands() {
|
||||
while (this._commands.firstChild)
|
||||
this._commands.removeChild(this._commands.firstChild);
|
||||
},
|
||||
|
||||
_positionOptions: function _positionOptions() {
|
||||
let options = {
|
||||
forcePosition: true
|
||||
};
|
||||
options.xPos = this._anchorRect.x;
|
||||
options.yPos = this._anchorRect.y + this._anchorRect.height;
|
||||
options.bottomAligned = false;
|
||||
options.leftAligned = true;
|
||||
options.source = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
|
||||
return options;
|
||||
},
|
||||
|
||||
show: function show(aAnchorRect, aSuggestionsList) {
|
||||
this._anchorRect = aAnchorRect;
|
||||
this._emptyCommands();
|
||||
for (let idx = 0; idx < aSuggestionsList.length; idx++) {
|
||||
let item = document.createElement("richlistitem");
|
||||
let label = document.createElement("label");
|
||||
label.setAttribute("value", aSuggestionsList[idx].label);
|
||||
item.setAttribute("data", aSuggestionsList[idx].value);
|
||||
item.appendChild(label);
|
||||
this._commands.appendChild(item);
|
||||
}
|
||||
|
||||
this._menuPopup.show(this._positionOptions());
|
||||
},
|
||||
|
||||
selectByIndex: function mn_selectByIndex(aIndex) {
|
||||
this._menuPopup.hide();
|
||||
FormHelperUI.doAutoComplete(this._commands.childNodes[aIndex].getAttribute("data"));
|
||||
},
|
||||
|
||||
hide: function hide () {
|
||||
this._menuPopup.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var ContextMenuUI = {
|
||||
_popupState: null,
|
||||
__menuPopup: null,
|
||||
|
||||
get _panel() { return document.getElementById("context-container"); },
|
||||
get _popup() { return document.getElementById("context-popup"); },
|
||||
get _commands() { return this._popup.childNodes[0]; },
|
||||
|
||||
get _menuPopup() {
|
||||
if (!this.__menuPopup)
|
||||
this.__menuPopup = new MenuPopup(this._panel, this._popup);
|
||||
|
||||
return this.__menuPopup;
|
||||
},
|
||||
|
||||
/*******************************************
|
||||
* External api
|
||||
*/
|
||||
|
||||
/*
|
||||
* popupState - return the json object for this context. Called
|
||||
* by context command to invoke actions on the target.
|
||||
*/
|
||||
get popupState() {
|
||||
return this._popupState;
|
||||
},
|
||||
|
||||
/*
|
||||
* showContextMenu - display a context sensitive menu based
|
||||
* on the data provided in a json data structure.
|
||||
*
|
||||
* @param aMessage data structure containing information about
|
||||
* the context.
|
||||
* aMessage.json - json data structure described below.
|
||||
* aMessage.target - target element on which to evoke
|
||||
*
|
||||
* @returns true if the context menu was displayed,
|
||||
* false otherwise.
|
||||
*
|
||||
* json: TBD
|
||||
*/
|
||||
showContextMenu: function ch_showContextMenu(aMessage) {
|
||||
this._popupState = aMessage.json;
|
||||
this._popupState.target = aMessage.target;
|
||||
|
||||
let contentTypes = this._popupState.types;
|
||||
|
||||
let optionsAvailable = false;
|
||||
for (let i = 0; i < this._commands.childElementCount; i++) {
|
||||
let command = this._commands.childNodes[i];
|
||||
command.hidden = true;
|
||||
|
||||
let types = command.getAttribute("type").split(/\s+/);
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
if (contentTypes.indexOf(types[i]) != -1) {
|
||||
optionsAvailable = true;
|
||||
command.hidden = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionsAvailable) {
|
||||
this._popupState = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
this._menuPopup.show(this._popupState);
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("CancelTouchSequence", true, false);
|
||||
if (this._popupState.target) {
|
||||
this._popupState.target.dispatchEvent(event);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
hide: function hide () {
|
||||
this._menuPopup.hide();
|
||||
this._popupState = null;
|
||||
},
|
||||
|
||||
reset: function reset() {
|
||||
this._popupState = null;
|
||||
}
|
||||
};
|
||||
|
||||
var MenuControlUI = {
|
||||
_currentControl: null,
|
||||
__menuPopup: null,
|
||||
|
||||
get _panel() { return document.getElementById("menucontrol-container"); },
|
||||
get _popup() { return document.getElementById("menucontrol-popup"); },
|
||||
get _commands() { return this._popup.childNodes[0]; },
|
||||
|
||||
get _menuPopup() {
|
||||
if (!this.__menuPopup)
|
||||
this.__menuPopup = new MenuPopup(this._panel, this._popup);
|
||||
|
||||
return this.__menuPopup;
|
||||
},
|
||||
|
||||
_firePopupEvent: function _firePopupEvent(aEventName) {
|
||||
let menupopup = this._currentControl.menupopup;
|
||||
if (menupopup.hasAttribute(aEventName)) {
|
||||
let func = new Function("event", menupopup.getAttribute(aEventName));
|
||||
func.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
_emptyCommands: function _emptyCommands() {
|
||||
while (this._commands.firstChild)
|
||||
this._commands.removeChild(this._commands.firstChild);
|
||||
},
|
||||
|
||||
_positionOptions: function _positionOptions() {
|
||||
let position = this._currentControl.menupopup.position || "after_start";
|
||||
let rect = this._currentControl.getBoundingClientRect();
|
||||
|
||||
let options = {
|
||||
forcePosition: true
|
||||
};
|
||||
|
||||
// TODO: Detect text direction and flip for RTL.
|
||||
|
||||
switch (position) {
|
||||
case "before_start":
|
||||
options.xPos = rect.left;
|
||||
options.yPos = rect.top;
|
||||
options.bottomAligned = true;
|
||||
options.leftAligned = true;
|
||||
break;
|
||||
case "before_end":
|
||||
options.xPos = rect.right;
|
||||
options.yPos = rect.top;
|
||||
options.bottomAligned = true;
|
||||
options.rightAligned = true;
|
||||
break;
|
||||
case "after_start":
|
||||
options.xPos = rect.left;
|
||||
options.yPos = rect.bottom;
|
||||
options.topAligned = true;
|
||||
options.leftAligned = true;
|
||||
break;
|
||||
case "after_end":
|
||||
options.xPos = rect.right;
|
||||
options.yPos = rect.bottom;
|
||||
options.topAligned = true;
|
||||
options.rightAligned = true;
|
||||
break;
|
||||
|
||||
// TODO: Support other popup positions.
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
show: function show(aMenuControl) {
|
||||
this._currentControl = aMenuControl;
|
||||
this._panel.setAttribute("for", aMenuControl.id);
|
||||
this._firePopupEvent("onpopupshowing");
|
||||
|
||||
this._emptyCommands();
|
||||
let children = this._currentControl.menupopup.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
let item = document.createElement("richlistitem");
|
||||
|
||||
if (child.disabled)
|
||||
item.setAttribute("disabled", "true");
|
||||
|
||||
if (child.hidden)
|
||||
item.setAttribute("hidden", "true");
|
||||
|
||||
// Add selected as a class name instead of an attribute to not being overidden
|
||||
// by the richlistbox behavior (it sets the "current" and "selected" attribute
|
||||
if (child.selected)
|
||||
item.setAttribute("class", "selected");
|
||||
|
||||
let image = document.createElement("image");
|
||||
image.setAttribute("src", child.image || "");
|
||||
item.appendChild(image);
|
||||
|
||||
let label = document.createElement("label");
|
||||
label.setAttribute("value", child.label);
|
||||
item.appendChild(label);
|
||||
|
||||
this._commands.appendChild(item);
|
||||
}
|
||||
|
||||
this._menuPopup.show(this._positionOptions());
|
||||
},
|
||||
|
||||
selectByIndex: function mn_selectByIndex(aIndex) {
|
||||
this._currentControl.selectedIndex = aIndex;
|
||||
|
||||
// Dispatch a xul command event to the attached menulist
|
||||
if (this._currentControl.dispatchEvent) {
|
||||
let evt = document.createEvent("XULCommandEvent");
|
||||
evt.initCommandEvent("command", true, true, window, 0, false, false, false, false, null);
|
||||
this._currentControl.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
this._menuPopup.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function MenuPopup(aPanel, aPopup) {
|
||||
this._panel = aPanel;
|
||||
this._popup = aPopup;
|
||||
this._wantTypeBehind = false;
|
||||
}
|
||||
|
||||
MenuPopup.prototype = {
|
||||
get _visible() { return !this._panel.hidden; },
|
||||
get _commands() { return this._popup.childNodes[0]; },
|
||||
|
||||
show: function (aPositionOptions) {
|
||||
if (this._visible)
|
||||
return;
|
||||
|
||||
window.addEventListener("keypress", this, true);
|
||||
window.addEventListener("mousedown", this, true);
|
||||
|
||||
this._panel.hidden = false;
|
||||
this._position(aPositionOptions || {});
|
||||
|
||||
let self = this;
|
||||
this._panel.addEventListener("transitionend", function () {
|
||||
self._panel.removeEventListener("transitionend", arguments.callee);
|
||||
self._panel.removeAttribute("showingfrom");
|
||||
});
|
||||
|
||||
let popupFrom = (aPositionOptions.forcePosition && !aPositionOptions.bottomAligned) ? "above" : "below";
|
||||
this._panel.setAttribute("showingfrom", popupFrom);
|
||||
|
||||
// Ensure the panel actually gets shifted before getting animated
|
||||
setTimeout(function () {
|
||||
self._panel.setAttribute("showing", "true");
|
||||
}, 0);
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
if (!this._visible)
|
||||
return;
|
||||
|
||||
window.removeEventListener("keypress", this, true);
|
||||
window.removeEventListener("mousedown", this, true);
|
||||
|
||||
let self = this;
|
||||
this._panel.addEventListener("transitionend", function () {
|
||||
self._panel.removeEventListener("transitionend", arguments.callee);
|
||||
self._panel.hidden = true;
|
||||
self._popupState = null;
|
||||
});
|
||||
|
||||
this._panel.removeAttribute("showing");
|
||||
},
|
||||
|
||||
_position: function _position(aPositionOptions) {
|
||||
let aX = aPositionOptions.xPos;
|
||||
let aY = aPositionOptions.yPos;
|
||||
let aSource = aPositionOptions.source;
|
||||
let forcePosition = aPositionOptions.forcePosition || false;
|
||||
let isRightAligned = aPositionOptions.rightAligned || false;
|
||||
let isBottomAligned = aPositionOptions.bottomAligned || false;
|
||||
|
||||
let width = this._popup.boxObject.width;
|
||||
let height = this._popup.boxObject.height;
|
||||
let halfWidth = width / 2;
|
||||
let halfHeight = height / 2;
|
||||
let screenWidth = ContentAreaObserver.width;
|
||||
let screenHeight = ContentAreaObserver.height;
|
||||
|
||||
if (forcePosition) {
|
||||
if (isRightAligned)
|
||||
aX -= width;
|
||||
|
||||
if (isBottomAligned)
|
||||
aY -= height;
|
||||
} else {
|
||||
let leftHand = MetroUtils.handPreference == MetroUtils.handPreferenceLeft;
|
||||
|
||||
// Add padding on the side of the menu per the user's hand preference
|
||||
if (aSource && aSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
|
||||
if (leftHand) {
|
||||
this._commands.setAttribute("left-hand", true);
|
||||
this._commands.removeAttribute("right-hand");
|
||||
} else {
|
||||
this._commands.setAttribute("right-hand", true);
|
||||
this._commands.removeAttribute("left-hand");
|
||||
}
|
||||
}
|
||||
|
||||
let hLeft = (aX - halfWidth - width - kPositionPadding) > kPositionPadding;
|
||||
let hRight = (aX + width + kPositionPadding) < screenWidth;
|
||||
let hCenter = (aX - halfWidth - kPositionPadding) > kPositionPadding;
|
||||
|
||||
let vTop = (aY - height - kPositionPadding) > kPositionPadding;
|
||||
let vCenter = (aY - halfHeight - kPositionPadding) > kPositionPadding &&
|
||||
aY + halfHeight < screenHeight;
|
||||
let vBottom = (aY + height + kPositionPadding) < screenHeight;
|
||||
|
||||
if (leftHand && hLeft && vCenter) {
|
||||
dump('leftHand && hLeft && vCenter\n');
|
||||
aX -= (width + halfWidth);
|
||||
aY -= halfHeight;
|
||||
} else if (!leftHand && hRight && vCenter) {
|
||||
dump('!leftHand && hRight && vCenter\n');
|
||||
aX += kPositionPadding;
|
||||
aY -= halfHeight;
|
||||
} else if (vBottom && hCenter) {
|
||||
dump('vBottom && hCenter\n');
|
||||
aX -= halfWidth;
|
||||
} else if (vTop && hCenter) {
|
||||
dump('vTop && hCenter\n');
|
||||
aX -= halfWidth;
|
||||
aY -= height;
|
||||
} else if (hCenter && vCenter) {
|
||||
dump('hCenter && vCenter\n');
|
||||
aX -= halfWidth;
|
||||
aY -= halfHeight;
|
||||
} else {
|
||||
dump('None, left hand: ' + leftHand + '!\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (aX < 0)
|
||||
aX = 0;
|
||||
|
||||
if (aY < 0)
|
||||
aY = 0;
|
||||
|
||||
this._panel.left = aX;
|
||||
this._panel.top = aY;
|
||||
|
||||
let excessY = (aY + height + kPositionPadding - screenHeight);
|
||||
this._popup.style.maxHeight = (excessY > 0) ? (height - excessY) + "px" : "none";
|
||||
|
||||
let excessX = (aX + width + kPositionPadding - screenWidth);
|
||||
this._popup.style.maxWidth = (excessX > 0) ? (width - excessX) + "px" : "none";
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "keypress":
|
||||
if (!this._wantTypeBehind) {
|
||||
// Hide the context menu so you can't type behind it.
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE)
|
||||
this.hide();
|
||||
}
|
||||
break;
|
||||
case "mousedown":
|
||||
if (!this._popup.contains(aEvent.target)) {
|
||||
aEvent.stopPropagation();
|
||||
this.hide();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Provide supports for Offline Applications
|
||||
*/
|
||||
var OfflineApps = {
|
||||
offlineAppRequested: function(aRequest, aTarget) {
|
||||
if (!Services.prefs.getBoolPref("browser.offline-apps.notify"))
|
||||
return;
|
||||
|
||||
let currentURI = Services.io.newURI(aRequest.location, aRequest.charset, null);
|
||||
|
||||
// don't bother showing UI if the user has already made a decision
|
||||
if (Services.perms.testExactPermission(currentURI, "offline-app") != Ci.nsIPermissionManager.UNKNOWN_ACTION)
|
||||
return;
|
||||
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) {
|
||||
// all pages can use offline capabilities, no need to ask the user
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
// this pref isn't set by default, ignore failures
|
||||
}
|
||||
|
||||
let host = currentURI.asciiHost;
|
||||
let notificationID = "offline-app-requested-" + host;
|
||||
let notificationBox = Browser.getNotificationBox(aTarget);
|
||||
|
||||
let notification = notificationBox.getNotificationWithValue(notificationID);
|
||||
let strings = Strings.browser;
|
||||
if (notification) {
|
||||
notification.documents.push(aRequest);
|
||||
} else {
|
||||
let buttons = [{
|
||||
label: strings.GetStringFromName("offlineApps.allow"),
|
||||
accessKey: null,
|
||||
callback: function() {
|
||||
for (let i = 0; i < notification.documents.length; i++)
|
||||
OfflineApps.allowSite(notification.documents[i], aTarget);
|
||||
}
|
||||
},{
|
||||
label: strings.GetStringFromName("offlineApps.never"),
|
||||
accessKey: null,
|
||||
callback: function() {
|
||||
for (let i = 0; i < notification.documents.length; i++)
|
||||
OfflineApps.disallowSite(notification.documents[i]);
|
||||
}
|
||||
},{
|
||||
label: strings.GetStringFromName("offlineApps.notNow"),
|
||||
accessKey: null,
|
||||
callback: function() { /* noop */ }
|
||||
}];
|
||||
|
||||
const priority = notificationBox.PRIORITY_INFO_LOW;
|
||||
let message = strings.formatStringFromName("offlineApps.available2", [host], 1);
|
||||
notification = notificationBox.appendNotification(message, notificationID, "", priority, buttons);
|
||||
notification.documents = [aRequest];
|
||||
}
|
||||
},
|
||||
|
||||
allowSite: function(aRequest, aTarget) {
|
||||
let currentURI = Services.io.newURI(aRequest.location, aRequest.charset, null);
|
||||
Services.perms.add(currentURI, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
|
||||
// When a site is enabled while loading, manifest resources will start
|
||||
// fetching immediately. This one time we need to do it ourselves.
|
||||
// The update must be started on the content process.
|
||||
aTarget.messageManager.sendAsyncMessage("Browser:MozApplicationCache:Fetch", aRequest);
|
||||
},
|
||||
|
||||
disallowSite: function(aRequest) {
|
||||
let currentURI = Services.io.newURI(aRequest.location, aRequest.charset, null);
|
||||
Services.perms.add(currentURI, "offline-app", Ci.nsIPermissionManager.DENY_ACTION);
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
if (aMessage.name == "Browser:MozApplicationManifest") {
|
||||
this.offlineAppRequested(aMessage.json, aMessage.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* SelectHelperUI: Provides an interface for making a choice in a list.
|
||||
* Supports simultaneous selection of choices and group headers.
|
||||
*/
|
||||
var SelectHelperUI = {
|
||||
_list: null,
|
||||
|
||||
get _container() {
|
||||
delete this._container;
|
||||
return this._container = document.getElementById("select-container");
|
||||
},
|
||||
|
||||
get _listbox() {
|
||||
delete this._listbox;
|
||||
return this._listbox = document.getElementById("select-commands");
|
||||
},
|
||||
|
||||
get _menuPopup() {
|
||||
let popup = document.getElementById("select-popup");
|
||||
delete this._menuPopup;
|
||||
return this._menuPopup = new MenuPopup(this._container, popup);
|
||||
},
|
||||
|
||||
show: function selectHelperShow(aList, aTitle, aRect) {
|
||||
if (AnimatedZoom.isZooming()) {
|
||||
FormHelperUI._waitForZoom(this.show.bind(this, aList, aTitle, aRect));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._list)
|
||||
this.reset();
|
||||
|
||||
this._list = aList;
|
||||
|
||||
// The element label is used as a title to give more context
|
||||
this._container.setAttribute("multiple", aList.multiple ? "true" : "false");
|
||||
|
||||
let firstSelected = null;
|
||||
|
||||
// Using a fragment prevent us to hang on huge list
|
||||
let fragment = document.createDocumentFragment();
|
||||
let choices = aList.choices;
|
||||
for (let i = 0; i < choices.length; i++) {
|
||||
let choice = choices[i];
|
||||
let item = document.createElement("listitem");
|
||||
|
||||
item.setAttribute("class", "option-command listitem-iconic action-button");
|
||||
item.setAttribute("flex", "1");
|
||||
item.setAttribute("crop", "center");
|
||||
item.setAttribute("label", choice.text);
|
||||
|
||||
choice.selected ? item.classList.add("selected")
|
||||
: item.classList.remove("selected");
|
||||
|
||||
choice.disabled ? item.setAttribute("disabled", "true")
|
||||
: item.removeAttribute("disabled");
|
||||
fragment.appendChild(item);
|
||||
|
||||
if (choice.group) {
|
||||
item.classList.add("optgroup");
|
||||
continue;
|
||||
}
|
||||
|
||||
item.optionIndex = choice.optionIndex;
|
||||
item.choiceIndex = i;
|
||||
|
||||
if (choice.inGroup)
|
||||
item.classList.add("in-optgroup");
|
||||
|
||||
if (choice.selected) {
|
||||
item.classList.add("selected");
|
||||
firstSelected = firstSelected || item;
|
||||
}
|
||||
}
|
||||
this._listbox.appendChild(fragment);
|
||||
|
||||
this._container.addEventListener("click", this, false);
|
||||
this._menuPopup.show(this._positionOptions(aRect));
|
||||
this._listbox.ensureElementIsVisible(firstSelected);
|
||||
},
|
||||
|
||||
reset: function selectHelperReset() {
|
||||
this._updateControl();
|
||||
while (this._listbox.hasChildNodes())
|
||||
this._listbox.removeChild(this._listbox.lastChild);
|
||||
this._list = null;
|
||||
},
|
||||
|
||||
hide: function selectHelperHide() {
|
||||
if (!this._list)
|
||||
return;
|
||||
|
||||
this._container.removeEventListener("click", this, false);
|
||||
this._menuPopup.hide();
|
||||
this.reset();
|
||||
},
|
||||
|
||||
_positionOptions: function _positionOptions(aRect) {
|
||||
let browser = Browser.selectedBrowser;
|
||||
let p0 = browser.transformBrowserToClient(aRect.left, aRect.top);
|
||||
let p1 = browser.transformBrowserToClient(aRect.right, aRect.bottom);
|
||||
|
||||
return {
|
||||
forcePosition: true,
|
||||
xPos: p0.x,
|
||||
yPos: p1.y,
|
||||
bottomAligned: false,
|
||||
leftAligned: true
|
||||
};
|
||||
},
|
||||
|
||||
_forEachOption: function _selectHelperForEachOption(aCallback) {
|
||||
let children = this._listbox.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let item = children[i];
|
||||
if (!item.hasOwnProperty("optionIndex"))
|
||||
continue;
|
||||
aCallback(item, i);
|
||||
}
|
||||
},
|
||||
|
||||
_updateControl: function _selectHelperUpdateControl() {
|
||||
Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceChange", { });
|
||||
},
|
||||
|
||||
handleEvent: function selectHelperHandleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
let item = aEvent.target;
|
||||
if (item && item.hasOwnProperty("optionIndex")) {
|
||||
if (this._list.multiple) {
|
||||
item.classList.toggle("selected");
|
||||
} else {
|
||||
item.classList.add("selected");
|
||||
}
|
||||
this.onSelect(item.optionIndex, item.classList.contains("selected"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onSelect: function selectHelperOnSelect(aIndex, aSelected) {
|
||||
Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceSelect", {
|
||||
index: aIndex,
|
||||
selected: aSelected
|
||||
});
|
||||
if (!this._list.multiple) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,678 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Markers
|
||||
*/
|
||||
|
||||
// Y axis scroll distance that will disable this module and cancel selection
|
||||
const kDisableOnScrollDistance = 25;
|
||||
|
||||
function MarkerDragger(aMarker) {
|
||||
this._marker = aMarker;
|
||||
}
|
||||
|
||||
MarkerDragger.prototype = {
|
||||
_selectionHelperUI: null,
|
||||
_marker: null,
|
||||
_shutdown: false,
|
||||
_dragging: false,
|
||||
|
||||
get marker() {
|
||||
return this._marker;
|
||||
},
|
||||
|
||||
set shutdown(aVal) {
|
||||
this._shutdown = aVal;
|
||||
},
|
||||
|
||||
get shutdown() {
|
||||
return this._shutdown;
|
||||
},
|
||||
|
||||
get dragging() {
|
||||
return this._dragging;
|
||||
},
|
||||
|
||||
freeDrag: function freeDrag() {
|
||||
return true;
|
||||
},
|
||||
|
||||
isDraggable: function isDraggable(aTarget, aContent) {
|
||||
return { x: true, y: true };
|
||||
},
|
||||
|
||||
dragStart: function dragStart(aX, aY, aTarget, aScroller) {
|
||||
if (this._shutdown)
|
||||
return false;
|
||||
this._dragging = true;
|
||||
this.marker.dragStart(aX, aY);
|
||||
return true;
|
||||
},
|
||||
|
||||
dragStop: function dragStop(aDx, aDy, aScroller) {
|
||||
if (this._shutdown)
|
||||
return false;
|
||||
this._dragging = false;
|
||||
this.marker.dragStop(aDx, aDy);
|
||||
return true;
|
||||
},
|
||||
|
||||
dragMove: function dragMove(aDx, aDy, aScroller, aIsKenetic, aClientX, aClientY) {
|
||||
// Note if aIsKenetic is true this is synthetic movement,
|
||||
// we don't want that so return false.
|
||||
if (this._shutdown || aIsKenetic)
|
||||
return false;
|
||||
this.marker.moveBy(aDx, aDy, aClientX, aClientY);
|
||||
// return true if we moved, false otherwise. The result
|
||||
// is used in deciding if we should repaint between drags.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function Marker() {}
|
||||
|
||||
Marker.prototype = {
|
||||
_element: null,
|
||||
_selectionHelperUI: null,
|
||||
_xPos: 0,
|
||||
_yPos: 0,
|
||||
_tag: "",
|
||||
_hPlane: 0,
|
||||
_vPlane: 0,
|
||||
|
||||
// Tweak me if the monocle graphics change in any way
|
||||
_monocleRadius: 8,
|
||||
_monocleXHitTextAdjust: -2,
|
||||
_monocleYHitTextAdjust: -10,
|
||||
|
||||
get xPos() {
|
||||
return this._xPos;
|
||||
},
|
||||
|
||||
get yPos() {
|
||||
return this._yPos;
|
||||
},
|
||||
|
||||
get tag() {
|
||||
return this._tag;
|
||||
},
|
||||
|
||||
set tag(aVal) {
|
||||
this._tag = aVal;
|
||||
},
|
||||
|
||||
get dragging() {
|
||||
return this._element.customDragger.dragging;
|
||||
},
|
||||
|
||||
init: function init(aParent, aElementId, xPos, yPos) {
|
||||
this._selectionHelperUI = aParent;
|
||||
this._element = document.getElementById(aElementId);
|
||||
// These get picked in input.js and receives drag input
|
||||
this._element.customDragger = new MarkerDragger(this);
|
||||
},
|
||||
|
||||
shutdown: function shutdown() {
|
||||
this._element.hidden = true;
|
||||
this._element.customDragger.shutdown = true;
|
||||
delete this._element.customDragger;
|
||||
this._selectionHelperUI = null;
|
||||
this._element = null;
|
||||
},
|
||||
|
||||
setTrackBounds: function setTrackBounds(aVerticalPlane, aHorizontalPlane) {
|
||||
// monocle boundaries
|
||||
this._hPlane = aHorizontalPlane;
|
||||
this._vPlane = aVerticalPlane;
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
this._element.hidden = false;
|
||||
},
|
||||
|
||||
hide: function hide() {
|
||||
this._element.hidden = true;
|
||||
},
|
||||
|
||||
position: function position(aX, aY) {
|
||||
if (aX < 0) {
|
||||
Util.dumpLn("Marker: aX is negative");
|
||||
aX = 0;
|
||||
}
|
||||
if (aY < 0) {
|
||||
Util.dumpLn("Marker: aY is negative");
|
||||
aY = 0;
|
||||
}
|
||||
this._xPos = aX;
|
||||
this._yPos = aY;
|
||||
this._setPosition();
|
||||
},
|
||||
|
||||
_setPosition: function _setPosition() {
|
||||
this._element.left = this._xPos + "px";
|
||||
this._element.top = this._yPos + "px";
|
||||
},
|
||||
|
||||
dragStart: function dragStart(aX, aY) {
|
||||
this._selectionHelperUI.markerDragStart(this);
|
||||
},
|
||||
|
||||
dragStop: function dragStop(aDx, aDy) {
|
||||
this._selectionHelperUI.markerDragStop(this);
|
||||
},
|
||||
|
||||
moveBy: function moveBy(aDx, aDy, aClientX, aClientY) {
|
||||
this._xPos -= aDx;
|
||||
this._yPos -= aDy;
|
||||
this._selectionHelperUI.markerDragMove(this);
|
||||
this._setPosition();
|
||||
},
|
||||
|
||||
hitTest: function hitTest(aX, aY) {
|
||||
// Gets the pointer of the arrow right in the middle of the
|
||||
// monocle.
|
||||
aY += this._monocleYHitTextAdjust;
|
||||
aX += this._monocleXHitTextAdjust;
|
||||
if (aX >= (this._xPos - this._monocleRadius) &&
|
||||
aX <= (this._xPos + this._monocleRadius) &&
|
||||
aY >= (this._yPos - this._monocleRadius) &&
|
||||
aY <= (this._yPos + this._monocleRadius))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* SelectionHelperUI
|
||||
*/
|
||||
|
||||
var SelectionHelperUI = {
|
||||
_debugEvents: false,
|
||||
_popupState: null,
|
||||
_startMark: null,
|
||||
_endMark: null,
|
||||
_target: null,
|
||||
_movement: { active: false, x:0, y: 0 },
|
||||
_activeSelectionRect: null,
|
||||
|
||||
get startMark() {
|
||||
if (this._startMark == null) {
|
||||
this._startMark = new Marker();
|
||||
this._startMark.tag = "start";
|
||||
this._startMark.init(this, "selectionhandle-start");
|
||||
}
|
||||
return this._startMark;
|
||||
},
|
||||
|
||||
get endMark() {
|
||||
if (this._endMark == null) {
|
||||
this._endMark = new Marker();
|
||||
this._endMark.tag = "end";
|
||||
this._endMark.init(this, "selectionhandle-end");
|
||||
}
|
||||
return this._endMark;
|
||||
},
|
||||
|
||||
get overlay() {
|
||||
return document.getElementById("selection-overlay");
|
||||
},
|
||||
|
||||
/*
|
||||
* openEditSession
|
||||
*
|
||||
* Attempts to select underlying text at a point and begins editing
|
||||
* the section.
|
||||
*/
|
||||
openEditSession: function openEditSession(aMessage) {
|
||||
if (!this.canHandle(aMessage))
|
||||
return;
|
||||
|
||||
/*
|
||||
* aMessage - from _onContentContextMenu in ContextMenuHandler
|
||||
* name: aMessage.name,
|
||||
* target: aMessage.target
|
||||
* json:
|
||||
* types: [],
|
||||
* label: "",
|
||||
* linkURL: "",
|
||||
* linkTitle: "",
|
||||
* linkProtocol: null,
|
||||
* mediaURL: "",
|
||||
* xPos: aEvent.x,
|
||||
* yPos: aEvent.y
|
||||
*/
|
||||
|
||||
this._popupState = aMessage.json;
|
||||
this._popupState._target = aMessage.target;
|
||||
|
||||
this._init();
|
||||
|
||||
Util.dumpLn("enableSelection target:", this._popupState._target);
|
||||
|
||||
// Set the track bounds for each marker NIY
|
||||
this.startMark.setTrackBounds(this._popupState.xPos, this._popupState.yPos);
|
||||
this.endMark.setTrackBounds(this._popupState.xPos, this._popupState.yPos);
|
||||
|
||||
// Send this over to SelectionHandler in content, they'll message us
|
||||
// back with information on the current selection.
|
||||
this._popupState._target.messageManager.sendAsyncMessage(
|
||||
"Browser:SelectionStart",
|
||||
{ xPos: this._popupState.xPos,
|
||||
yPos: this._popupState.yPos });
|
||||
|
||||
this._setupDebugOptions();
|
||||
},
|
||||
|
||||
/*
|
||||
* canHandle
|
||||
*
|
||||
* Determines if we can handle a ContextMenuHandler message.
|
||||
*/
|
||||
canHandle: function canHandle(aMessage) {
|
||||
// Reject empty text inputs, nothing to do here
|
||||
if (aMessage.json.types.indexOf("input-empty") != -1) {
|
||||
return false;
|
||||
}
|
||||
// Input with text or general text in a page
|
||||
if (aMessage.json.types.indexOf("content-text") == -1 &&
|
||||
aMessage.json.types.indexOf("input-text") == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/*
|
||||
* isActive
|
||||
*
|
||||
* Determines if an edit session is currently active.
|
||||
*/
|
||||
isActive: function isActive() {
|
||||
return (this._popupState && this._popupState._target);
|
||||
},
|
||||
|
||||
/*
|
||||
* closeEditSession
|
||||
*
|
||||
* Closes an active edit session and shuts down. Does not clear existing
|
||||
* selection regions if they exist.
|
||||
*/
|
||||
closeEditSession: function closeEditSession() {
|
||||
if (this._popupState._target)
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionClose");
|
||||
this._shutdown();
|
||||
},
|
||||
|
||||
/*
|
||||
* closeEditSessionAndClear
|
||||
*
|
||||
* Closes an active edit session and shuts down. Clears any selection region
|
||||
* associated with the edit session.
|
||||
*/
|
||||
closeEditSessionAndClear: function closeEditSessionAndClear() {
|
||||
if (this._popupState._target)
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionClear");
|
||||
this.closeEditSession();
|
||||
},
|
||||
|
||||
/*
|
||||
* Internal
|
||||
*/
|
||||
|
||||
_init: function _init() {
|
||||
// SelectionHandler messages
|
||||
messageManager.addMessageListener("Content:SelectionRange", this);
|
||||
messageManager.addMessageListener("Content:SelectionCopied", this);
|
||||
messageManager.addMessageListener("Content:SelectionFail", this);
|
||||
messageManager.addMessageListener("Content:SelectionDebugRect", this);
|
||||
|
||||
// selection related events
|
||||
window.addEventListener("click", this, true);
|
||||
window.addEventListener("dblclick", this, true);
|
||||
window.addEventListener("MozPressTapGesture", this, true);
|
||||
|
||||
// Picking up scroll attempts
|
||||
window.addEventListener("touchstart", this, true);
|
||||
window.addEventListener("touchend", this, true);
|
||||
window.addEventListener("touchmove", this, true);
|
||||
|
||||
// cancellation related events
|
||||
window.addEventListener("keypress", this, true);
|
||||
Elements.browsers.addEventListener("URLChanged", this, true);
|
||||
Elements.browsers.addEventListener("SizeChanged", this, true);
|
||||
Elements.browsers.addEventListener("ZoomChanged", this, true);
|
||||
|
||||
window.addEventListener("MozPrecisePointer", this, true);
|
||||
|
||||
this.overlay.enabled = true;
|
||||
},
|
||||
|
||||
_shutdown: function _shutdown() {
|
||||
messageManager.removeMessageListener("Content:SelectionRange", this);
|
||||
messageManager.removeMessageListener("Content:SelectionCopied", this);
|
||||
messageManager.removeMessageListener("Content:SelectionFail", this);
|
||||
messageManager.removeMessageListener("Content:SelectionDebugRect", this);
|
||||
|
||||
window.removeEventListener("click", this, true);
|
||||
window.removeEventListener("dblclick", this, true);
|
||||
window.removeEventListener("MozPressTapGesture", this, true);
|
||||
|
||||
window.removeEventListener("touchstart", this, true);
|
||||
window.removeEventListener("touchend", this, true);
|
||||
window.removeEventListener("touchmove", this, true);
|
||||
|
||||
window.removeEventListener("keypress", this, true);
|
||||
Elements.browsers.removeEventListener("URLChanged", this, true);
|
||||
Elements.browsers.removeEventListener("SizeChanged", this, true);
|
||||
Elements.browsers.removeEventListener("ZoomChanged", this, true);
|
||||
|
||||
window.removeEventListener("MozPrecisePointer", this, true);
|
||||
|
||||
if (this.startMark != null)
|
||||
this.startMark.shutdown();
|
||||
if (this.endMark != null)
|
||||
this.endMark.shutdown();
|
||||
|
||||
delete this._startMark;
|
||||
delete this._endMark;
|
||||
|
||||
this._popupState = null;
|
||||
this._activeSelectionRect = null;
|
||||
|
||||
this.overlay.displayDebugLayer = false;
|
||||
this.overlay.enabled = false;
|
||||
},
|
||||
|
||||
/*
|
||||
* _setupDebugOptions
|
||||
*
|
||||
* Sends a message over to content instructing it to
|
||||
* turn on various debug features.
|
||||
*/
|
||||
_setupDebugOptions: function _setupDebugOptions() {
|
||||
// Debug options for selection
|
||||
let debugOpts = { dumpRanges: false, displayRanges: false, dumpEvents: false };
|
||||
try {
|
||||
if (Services.prefs.getBoolPref(kDebugSelectionDumpPref))
|
||||
debugOpts.displayRanges = true;
|
||||
} catch (ex) {}
|
||||
try {
|
||||
if (Services.prefs.getBoolPref(kDebugSelectionDisplayPref))
|
||||
debugOpts.displayRanges = true;
|
||||
} catch (ex) {}
|
||||
try {
|
||||
if (Services.prefs.getBoolPref(kDebugSelectionDumpEvents)) {
|
||||
debugOpts.dumpEvents = true;
|
||||
this._debugEvents = true;
|
||||
}
|
||||
} catch (ex) {}
|
||||
|
||||
if (debugOpts.displayRanges || debugOpts.dumpRanges || debugOpts.dumpEvents) {
|
||||
// Turn on the debug layer
|
||||
this.overlay.displayDebugLayer = true;
|
||||
// Tell SelectionHandler what to do
|
||||
this._popupState
|
||||
._target
|
||||
.messageManager
|
||||
.sendAsyncMessage("Browser:SelectionDebug", debugOpts);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Event handlers for document events
|
||||
*/
|
||||
|
||||
_checkForActiveDrag: function _checkForActiveDrag() {
|
||||
return (this.startMark.dragging || this.endMark.dragging);
|
||||
},
|
||||
|
||||
_hitTestSelection: function _hitTestSelection(aEvent) {
|
||||
let clickPos =
|
||||
this._popupState._target.transformClientToBrowser(aEvent.clientX,
|
||||
aEvent.clientY);
|
||||
// Ignore if the double tap isn't on our active selection rect.
|
||||
if (this._activeSelectionRect &&
|
||||
Util.pointWithinRect(clickPos.x, clickPos.y, this._activeSelectionRect)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_onTap: function _onTap(aEvent) {
|
||||
// Trap single clicks which if forwarded to content will clear selection.
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
|
||||
if (this.startMark.hitTest(aEvent.clientX, aEvent.clientY) ||
|
||||
this.endMark.hitTest(aEvent.clientX, aEvent.clientY)) {
|
||||
// NIY
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:ChangeMode", {});
|
||||
}
|
||||
},
|
||||
|
||||
_onLongTap: function _onLongTap(aEvent) {
|
||||
// Check to see if this tap is on one of our monocles. Tap is
|
||||
// easy to trigger when dragging them around.
|
||||
if (this.startMark.hitTest(aEvent.clientX, aEvent.clientY) ||
|
||||
this.endMark.hitTest(aEvent.clientX, aEvent.clientY)) {
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks to see if the tap event was on our selection rect.
|
||||
* If it is, we select the underlying text and shutdown.
|
||||
*/
|
||||
_onDblTap: function _onDblTap(aEvent) {
|
||||
if (!this._hitTestSelection(aEvent)) {
|
||||
// Clear and close
|
||||
this.closeEditSessionAndClear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Select and close
|
||||
let clickPos =
|
||||
this._popupState._target.transformClientToBrowser(aEvent.clientX,
|
||||
aEvent.clientY);
|
||||
let json = {
|
||||
xPos: clickPos.x,
|
||||
yPos: clickPos.y,
|
||||
};
|
||||
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionCopy", json);
|
||||
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
_onSelectionCopied: function _onSelectionCopied(json) {
|
||||
if (json.succeeded) {
|
||||
this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied"));
|
||||
}
|
||||
this.closeEditSessionAndClear();
|
||||
},
|
||||
|
||||
_onSelectionRangeChange: function _onSelectionRangeChange(json) {
|
||||
if (json.updateStart) {
|
||||
let pos =
|
||||
this._popupState._target.transformBrowserToClient(json.start.xPos, json.start.yPos);
|
||||
this.startMark.position(pos.x, pos.y);
|
||||
this.startMark.show();
|
||||
}
|
||||
if (json.updateEnd) {
|
||||
let pos =
|
||||
this._popupState._target.transformBrowserToClient(json.end.xPos, json.end.yPos);
|
||||
this.endMark.position(pos.x, pos.y);
|
||||
this.endMark.show();
|
||||
}
|
||||
this._activeSelectionRect = json.rect;
|
||||
},
|
||||
|
||||
_onSelectionFail: function _onSelectionFail() {
|
||||
Util.dumpLn("failed to get a selection.");
|
||||
this.closeEditSession();
|
||||
},
|
||||
|
||||
_onKeypress: function _onKeypress() {
|
||||
this.closeEditSession();
|
||||
},
|
||||
|
||||
_onResize: function _onResize() {
|
||||
this._popupState._target
|
||||
.messageManager
|
||||
.sendAsyncMessage("Browser:SelectionUpdate", {});
|
||||
},
|
||||
|
||||
_onDebugRectRequest: function _onDebugRectRequest(aMsg) {
|
||||
this.overlay.addDebugRect(aMsg.left, aMsg.top, aMsg.right, aMsg.bottom,
|
||||
aMsg.color, aMsg.fill, aMsg.id);
|
||||
},
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
_initMouseEventFromEvent: function _initMouseEventFromEvent(aDestEvent, aSrcEvent, aType) {
|
||||
event.initNSMouseEvent(aType, true, true, content, 0,
|
||||
aSrcEvent.screenX, aSrcEvent.screenY, aSrcEvent.clientX, aSrcEvent.clientY,
|
||||
false, false, false, false,
|
||||
aSrcEvent.button, aSrcEvent.relatedTarget, 1.0,
|
||||
Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
if (this._debugEvents && aEvent.type != "touchmove") {
|
||||
Util.dumpLn("SelectionHelperUI:", aEvent.type);
|
||||
}
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
this._onTap(aEvent);
|
||||
break;
|
||||
|
||||
case "dblclick":
|
||||
this._onDblTap(aEvent);
|
||||
break;
|
||||
|
||||
case "MozPressTapGesture":
|
||||
this._onLongTap(aEvent);
|
||||
break;
|
||||
|
||||
case "touchstart": {
|
||||
if (aEvent.touches.length != 1)
|
||||
break;
|
||||
let touch = aEvent.touches[0];
|
||||
this._movement.x = this._movement.y = 0;
|
||||
this._movement.x = touch.clientX;
|
||||
this._movement.y = touch.clientY;
|
||||
this._movement.active = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "touchend":
|
||||
if (aEvent.touches.length == 0)
|
||||
this._movement.active = false;
|
||||
break;
|
||||
|
||||
case "touchmove": {
|
||||
if (aEvent.touches.length != 1)
|
||||
break;
|
||||
let touch = aEvent.touches[0];
|
||||
// Clear our selection overlay when the user starts to pan the page
|
||||
if (!this._checkForActiveDrag() && this._movement.active) {
|
||||
let distanceY = touch.clientY - this._movement.y;
|
||||
if (Math.abs(distanceY) > kDisableOnScrollDistance) {
|
||||
this.closeEditSessionAndClear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "keypress":
|
||||
this._onKeypress(aEvent);
|
||||
break;
|
||||
|
||||
case "SizeChanged":
|
||||
this._onResize(aEvent);
|
||||
break;
|
||||
|
||||
case "ZoomChanged":
|
||||
case "URLChanged":
|
||||
case "MozPrecisePointer":
|
||||
this.closeEditSessionAndClear();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function sh_receiveMessage(aMessage) {
|
||||
if (this._debugEvents) Util.dumpLn("SelectionHelperUI:", aMessage.name);
|
||||
let json = aMessage.json;
|
||||
switch (aMessage.name) {
|
||||
case "Content:SelectionFail":
|
||||
this._onSelectionFail();
|
||||
break;
|
||||
case "Content:SelectionRange":
|
||||
this._onSelectionRangeChange(json);
|
||||
break;
|
||||
case "Content:SelectionCopied":
|
||||
this._onSelectionCopied(json);
|
||||
break;
|
||||
case "Content:SelectionDebugRect":
|
||||
this._onDebugRectRequest(json);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Callbacks from markers
|
||||
*/
|
||||
|
||||
_getMarkerBaseMessage: function _getMarkerBaseMessage() {
|
||||
/*
|
||||
This appears to be adjusted for scroll and scale. It should only
|
||||
adjust for scale, content handles scroll offsets.
|
||||
let startPos =
|
||||
this._popupState._target.transformBrowserToClient(this.startMark.xPos,
|
||||
this.startMark.yPos);
|
||||
let endPos =
|
||||
this._popupState._target.transformBrowserToClient(this.endMark.xPos,
|
||||
this.endMark.yPos);
|
||||
return {
|
||||
start: { xPos: startPos.x, yPos: startPos.y },
|
||||
end: { xPos: endPos.x, yPos: endPos.y },
|
||||
};
|
||||
*/
|
||||
return {
|
||||
start: { xPos: this.startMark.xPos, yPos: this.startMark.yPos },
|
||||
end: { xPos: this.endMark.xPos, yPos: this.endMark.yPos },
|
||||
};
|
||||
},
|
||||
|
||||
markerDragStart: function markerDragStart(aMarker) {
|
||||
let json = this._getMarkerBaseMessage();
|
||||
json.change = aMarker.tag;
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionMoveStart", json);
|
||||
},
|
||||
|
||||
markerDragStop: function markerDragStop(aMarker) {
|
||||
//aMarker.show();
|
||||
let json = this._getMarkerBaseMessage();
|
||||
json.change = aMarker.tag;
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionMoveEnd", json);
|
||||
},
|
||||
|
||||
markerDragMove: function markerDragMove(aMarker) {
|
||||
let json = this._getMarkerBaseMessage();
|
||||
json.change = aMarker.tag;
|
||||
this._popupState._target.messageManager.sendAsyncMessage("Browser:SelectionMove", json);
|
||||
},
|
||||
|
||||
showToast: function showToast(aString) {
|
||||
let toaster =
|
||||
Cc["@mozilla.org/toaster-alerts-service;1"]
|
||||
.getService(Ci.nsIAlertsService);
|
||||
toaster.showAlertNotification(null, aString, "", false, "", null);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
/* 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/. */
|
||||
|
||||
var SharingUI = {
|
||||
_dialog: null,
|
||||
|
||||
show: function show(aURL, aTitle) {
|
||||
try {
|
||||
this.showSharingUI(aURL, aTitle);
|
||||
} catch (ex) {
|
||||
this.showFallback(aURL, aTitle);
|
||||
}
|
||||
},
|
||||
|
||||
showSharingUI: function showSharingUI(aURL, aTitle) {
|
||||
let sharingSvc = Cc["@mozilla.org/uriloader/external-sharing-app-service;1"].getService(Ci.nsIExternalSharingAppService);
|
||||
sharingSvc.shareWithDefault(aURL, "text/plain", aTitle);
|
||||
},
|
||||
|
||||
showFallback: function showFallback(aURL, aTitle) {
|
||||
this._dialog = DialogUI.importModal(window, "chrome://browser/content/prompt/share.xul", null);
|
||||
document.getElementById("share-title").value = aTitle || aURL;
|
||||
|
||||
DialogUI.pushPopup(this, this._dialog);
|
||||
|
||||
let bbox = document.getElementById("share-buttons-box");
|
||||
this._handlers.forEach(function(handler) {
|
||||
let button = document.createElement("button");
|
||||
button.className = "action-button";
|
||||
button.setAttribute("label", handler.name);
|
||||
button.addEventListener("command", function() {
|
||||
SharingUI.hide();
|
||||
handler.callback(aURL || "", aTitle || "");
|
||||
}, false);
|
||||
bbox.appendChild(button);
|
||||
});
|
||||
|
||||
this._dialog.waitForClose();
|
||||
DialogUI.popPopup(this);
|
||||
},
|
||||
|
||||
hide: function hide() {
|
||||
this._dialog.close();
|
||||
this._dialog = null;
|
||||
},
|
||||
|
||||
_handlers: [
|
||||
{
|
||||
name: "Email",
|
||||
callback: function callback(aURL, aTitle) {
|
||||
let url = "mailto:?subject=" + encodeURIComponent(aTitle) +
|
||||
"&body=" + encodeURIComponent(aURL);
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
let extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
|
||||
.getService(Ci.nsIExternalProtocolService);
|
||||
extProtocolSvc.loadUrl(uri);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Twitter",
|
||||
callback: function callback(aURL, aTitle) {
|
||||
let url = "http://twitter.com/home?status=" + encodeURIComponent((aTitle ? aTitle+": " : "")+aURL);
|
||||
BrowserUI.newTab(url, Browser.selectedTab);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Google Reader",
|
||||
callback: function callback(aURL, aTitle) {
|
||||
let url = "http://www.google.com/reader/link?url=" + encodeURIComponent(aURL) +
|
||||
"&title=" + encodeURIComponent(aTitle);
|
||||
BrowserUI.newTab(url, Browser.selectedTab);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Facebook",
|
||||
callback: function callback(aURL, aTitle) {
|
||||
let url = "http://www.facebook.com/share.php?u=" + encodeURIComponent(aURL);
|
||||
BrowserUI.newTab(url, Browser.selectedTab);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
|
||||
// TODO <jwilde>: Observe changes in history with nsINavHistoryObserver
|
||||
function HistoryView(aSet) {
|
||||
this._set = aSet;
|
||||
this._set.controller = this;
|
||||
|
||||
//add observers here
|
||||
}
|
||||
|
||||
HistoryView.prototype = {
|
||||
_set:null,
|
||||
|
||||
handleItemClick: function tabview_handleItemClick(aItem) {
|
||||
let url = aItem.getAttribute("value");
|
||||
BrowserUI.goToURI(url);
|
||||
},
|
||||
|
||||
populateGrid: function populateGrid() {
|
||||
let query = gHistSvc.getNewQuery();
|
||||
let options = gHistSvc.getNewQueryOptions();
|
||||
options.excludeQueries = true;
|
||||
options.queryType = options.QUERY_TYPE_HISTORY;
|
||||
options.maxResults = StartUI.maxResultsPerSection;
|
||||
options.resultType = options.RESULTS_AS_URI;
|
||||
options.sortingMode = options.SORT_BY_DATE_DESCENDING;
|
||||
|
||||
let result = gHistSvc.executeQuery(query, options);
|
||||
let rootNode = result.root;
|
||||
rootNode.containerOpen = true;
|
||||
let childCount = rootNode.childCount;
|
||||
|
||||
for (let i = 0; i < childCount; i++) {
|
||||
let node = rootNode.getChild(i);
|
||||
let uri = node.uri;
|
||||
let title = node.title || uri;
|
||||
|
||||
let item = this._set.appendItem(title, uri);
|
||||
item.setAttribute("iconURI", node.icon);
|
||||
}
|
||||
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
},
|
||||
};
|
||||
|
||||
let HistoryStartView = {
|
||||
_view: null,
|
||||
get _grid() { return document.getElementById("start-history-grid"); },
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
},
|
||||
|
||||
init: function init() {
|
||||
this._view = new HistoryView(this._grid);
|
||||
this._view.populateGrid();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
}
|
||||
};
|
||||
|
||||
let HistoryPanelView = {
|
||||
_view: null,
|
||||
get _grid() { return document.getElementById("history-list"); },
|
||||
get visible() { return PanelUI.isPaneVisible("history-container"); },
|
||||
|
||||
show: function show() {
|
||||
this._grid.arrangeItems();
|
||||
},
|
||||
|
||||
init: function init() {
|
||||
this._view = new HistoryView(this._grid);
|
||||
this._view.populateGrid();
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
this._view.destruct();
|
||||
}
|
||||
};
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,770 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html onclick="keepFocusInTextbox(event)">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<title>JavaScript Shell 1.4</title>
|
||||
|
||||
<script type="text/javascript">
|
||||
var
|
||||
histList = [""],
|
||||
histPos = 0,
|
||||
_scope = {},
|
||||
_win, // a top-level context
|
||||
question, // {String} the input command that's being evaluated. Accessed via
|
||||
// |Shell.question| from the target window for evaluation.
|
||||
_in, // {HTMLTextAreaElement} the textarea containing the input
|
||||
_out, // {HTMLDivElement} the output is printed to this element
|
||||
tooManyMatches = null,
|
||||
lastError = null,
|
||||
_jsVer = 0 // determines the way to execute the commands, see run()
|
||||
;
|
||||
|
||||
function refocus()
|
||||
{
|
||||
_in.blur(); // Needed for Mozilla to scroll correctly.
|
||||
_in.focus();
|
||||
}
|
||||
|
||||
function init()
|
||||
{
|
||||
_in = document.getElementById("input");
|
||||
_out = document.getElementById("output");
|
||||
|
||||
_win = window;
|
||||
|
||||
if (opener && !opener.closed)
|
||||
{
|
||||
println("Using bookmarklet version of shell: commands will run in opener's context.", "message");
|
||||
_win = opener;
|
||||
}
|
||||
|
||||
/* Run a series of (potentially async, but quick) tests to determine the
|
||||
* way to run code in this browser (for run()). Sets window._jsVer based
|
||||
* on the tests. */
|
||||
_jsVer = 0;
|
||||
for (var jsVerToTry=19; jsVerToTry>=15; jsVerToTry--) {
|
||||
run(window, "if(_jsVer < " + jsVerToTry + ") { " +
|
||||
"_jsVer=" + jsVerToTry + "; }", jsVerToTry);
|
||||
}
|
||||
|
||||
initTarget();
|
||||
|
||||
recalculateInputHeight();
|
||||
refocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs |code| in |_win|'s context.
|
||||
* @param overridenJSVer {int} (optional) overrides the default (specified by _jsVer)
|
||||
* way to run the code.
|
||||
*/
|
||||
function run(_win, code, overridenJSVer) {
|
||||
var jsVerToUse = overridenJSVer ? overridenJSVer : _jsVer;
|
||||
if (jsVerToUse <= 15) {
|
||||
_win.location.href = "javascript:" + code + "; void 0";
|
||||
} else {
|
||||
var sc = _win.document.createElement("script");
|
||||
sc.type="application/javascript;version=" + jsVerToUse/10;
|
||||
sc.src="data:application/x-javascript," + code;
|
||||
_win.document.body.appendChild(sc); // runs the script asynchronously
|
||||
}
|
||||
}
|
||||
|
||||
function initTarget()
|
||||
{
|
||||
_win.Shell = window;
|
||||
_win.print = shellCommands.print;
|
||||
}
|
||||
|
||||
|
||||
// Unless the user is selected something, refocus the textbox.
|
||||
// (requested by caillon, brendan, asa)
|
||||
function keepFocusInTextbox(e)
|
||||
{
|
||||
var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard
|
||||
|
||||
while (!g.tagName)
|
||||
g = g.parentNode;
|
||||
var t = g.tagName.toUpperCase();
|
||||
if (t=="A" || t=="INPUT")
|
||||
return;
|
||||
|
||||
if (window.getSelection) {
|
||||
// Mozilla
|
||||
if (String(window.getSelection()))
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// IE
|
||||
if ( document.selection.createRange().text )
|
||||
return;
|
||||
}
|
||||
|
||||
refocus();
|
||||
}
|
||||
|
||||
function inputKeydown(e) {
|
||||
// Use onkeydown because IE doesn't support onkeypress for arrow keys
|
||||
|
||||
//alert(e.keyCode + " ^ " + e.keycode);
|
||||
|
||||
if (e.shiftKey && e.keyCode == 13) { // shift-enter
|
||||
// don't do anything; allow the shift-enter to insert a line break as normal
|
||||
} else if (e.keyCode == 13) { // enter
|
||||
// execute the input on enter
|
||||
try { go(); } catch(er) { alert(er); };
|
||||
setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later
|
||||
} else if (e.keyCode == 38) { // up
|
||||
// go up in history if at top or ctrl-up
|
||||
if (e.ctrlKey || caretInFirstLine(_in))
|
||||
hist(true);
|
||||
} else if (e.keyCode == 40) { // down
|
||||
// go down in history if at end or ctrl-down
|
||||
if (e.ctrlKey || caretInLastLine(_in))
|
||||
hist(false);
|
||||
} else if (e.keyCode == 9) { // tab
|
||||
tabcomplete();
|
||||
setTimeout(function() { refocus(); }, 0); // refocus because tab was hit
|
||||
} else { }
|
||||
|
||||
setTimeout(recalculateInputHeight, 0);
|
||||
|
||||
//return true;
|
||||
};
|
||||
|
||||
function caretInFirstLine(textbox)
|
||||
{
|
||||
// IE doesn't support selectionStart/selectionEnd
|
||||
if (textbox.selectionStart == undefined)
|
||||
return true;
|
||||
|
||||
var firstLineBreak = textbox.value.indexOf("\n");
|
||||
|
||||
return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak));
|
||||
}
|
||||
|
||||
function caretInLastLine(textbox)
|
||||
{
|
||||
// IE doesn't support selectionStart/selectionEnd
|
||||
if (textbox.selectionEnd == undefined)
|
||||
return true;
|
||||
|
||||
var lastLineBreak = textbox.value.lastIndexOf("\n");
|
||||
|
||||
return (textbox.selectionEnd > lastLineBreak);
|
||||
}
|
||||
|
||||
function recalculateInputHeight()
|
||||
{
|
||||
var rows = _in.value.split(/\n/).length
|
||||
+ 1 // prevent scrollbar flickering in Mozilla
|
||||
+ (window.opera ? 1 : 0); // leave room for scrollbar in Opera
|
||||
|
||||
if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.
|
||||
_in.rows = rows;
|
||||
}
|
||||
|
||||
function println(s, type)
|
||||
{
|
||||
if((s=String(s)))
|
||||
{
|
||||
var newdiv = document.createElement("div");
|
||||
newdiv.appendChild(document.createTextNode(s));
|
||||
newdiv.className = type;
|
||||
_out.appendChild(newdiv);
|
||||
return newdiv;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function printWithRunin(h, s, type)
|
||||
{
|
||||
var div = println(s, type);
|
||||
if (div) {
|
||||
var head = document.createElement("strong");
|
||||
head.appendChild(document.createTextNode(h + ": "));
|
||||
div.insertBefore(head, div.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var shellCommands =
|
||||
{
|
||||
load : function load(url)
|
||||
{
|
||||
var s = _win.document.createElement("script");
|
||||
s.type = "text/javascript";
|
||||
s.src = url;
|
||||
_win.document.getElementsByTagName("head")[0].appendChild(s);
|
||||
println("Loading " + url + "...", "message");
|
||||
},
|
||||
|
||||
clear : function clear()
|
||||
{
|
||||
var CHILDREN_TO_PRESERVE = 3;
|
||||
while (_out.childNodes[CHILDREN_TO_PRESERVE])
|
||||
_out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]);
|
||||
},
|
||||
|
||||
print : function print(s) { println(s, "print"); },
|
||||
|
||||
// the normal function, "print", shouldn't return a value
|
||||
// (suggested by brendan; later noticed it was a problem when showing others)
|
||||
pr : function pr(s)
|
||||
{
|
||||
shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()!
|
||||
return s;
|
||||
},
|
||||
|
||||
props : function props(e, onePerLine)
|
||||
{
|
||||
if (e === null) {
|
||||
println("props called with null argument", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e === undefined) {
|
||||
println("props called with undefined argument", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
var ns = ["Methods", "Fields", "Unreachables"];
|
||||
var as = [[], [], []]; // array of (empty) arrays of arrays!
|
||||
var p, j, i; // loop variables, several used multiple times
|
||||
|
||||
var protoLevels = 0;
|
||||
|
||||
for (p = e; p; p = p.__proto__)
|
||||
{
|
||||
for (i=0; i<ns.length; ++i)
|
||||
as[i][protoLevels] = [];
|
||||
++protoLevels;
|
||||
}
|
||||
|
||||
for(var a in e)
|
||||
{
|
||||
// Shortcoming: doesn't check that VALUES are the same in object and prototype.
|
||||
|
||||
var protoLevel = -1;
|
||||
try
|
||||
{
|
||||
for (p = e; p && (a in p); p = p.__proto__)
|
||||
++protoLevel;
|
||||
}
|
||||
catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string
|
||||
|
||||
var type = 1;
|
||||
try
|
||||
{
|
||||
if ((typeof e[a]) == "function")
|
||||
type = 0;
|
||||
}
|
||||
catch (er) { type = 2; }
|
||||
|
||||
as[type][protoLevel].push(a);
|
||||
}
|
||||
|
||||
function times(s, n) { return n ? s + times(s, n-1) : ""; }
|
||||
|
||||
for (j=0; j<protoLevels; ++j)
|
||||
for (i=0;i<ns.length;++i)
|
||||
if (as[i][j].length)
|
||||
printWithRunin(
|
||||
ns[i] + times(" of prototype", j),
|
||||
(onePerLine ? "\n\n" : "") + as[i][j].sort().join(onePerLine ? "\n" : ", ") + (onePerLine ? "\n\n" : ""),
|
||||
"propList"
|
||||
);
|
||||
},
|
||||
|
||||
blink : function blink(node)
|
||||
{
|
||||
if (!node) throw("blink: argument is null or undefined.");
|
||||
if (node.nodeType == null) throw("blink: argument must be a node.");
|
||||
if (node.nodeType == 3) throw("blink: argument must not be a text node");
|
||||
if (node.documentElement) throw("blink: argument must not be the document object");
|
||||
|
||||
function setOutline(o) {
|
||||
return function() {
|
||||
if (node.style.outline != node.style.bogusProperty) {
|
||||
// browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8).
|
||||
node.style.outline = o;
|
||||
}
|
||||
else if (node.style.MozOutline != node.style.bogusProperty) {
|
||||
// browser supports MozOutline (Firefox 1.0.x and older)
|
||||
node.style.MozOutline = o;
|
||||
}
|
||||
else {
|
||||
// browser only supports border (IE). border is a fallback because it moves things around.
|
||||
node.style.border = o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function focusIt(a) {
|
||||
return function() {
|
||||
a.focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (node.ownerDocument) {
|
||||
var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE
|
||||
if (windowToFocusNow)
|
||||
setTimeout(focusIt(windowToFocusNow.top), 0);
|
||||
}
|
||||
|
||||
for(var i=1;i<7;++i)
|
||||
setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100);
|
||||
|
||||
setTimeout(focusIt(window), 800);
|
||||
setTimeout(focusIt(_in), 810);
|
||||
},
|
||||
|
||||
scope : function scope(sc)
|
||||
{
|
||||
if (!sc) sc = {};
|
||||
_scope = sc;
|
||||
println("Scope is now " + sc + ". If a variable is not found in this scope, window will also be searched. New variables will still go on window.", "message");
|
||||
},
|
||||
|
||||
mathHelp : function mathHelp()
|
||||
{
|
||||
printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList");
|
||||
printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList");
|
||||
},
|
||||
|
||||
ans : undefined
|
||||
};
|
||||
|
||||
|
||||
function hist(up)
|
||||
{
|
||||
// histList[0] = first command entered, [1] = second, etc.
|
||||
// type something, press up --> thing typed is now in "limbo"
|
||||
// (last item in histList) and should be reachable by pressing
|
||||
// down again.
|
||||
|
||||
var L = histList.length;
|
||||
|
||||
if (L == 1)
|
||||
return;
|
||||
|
||||
if (up)
|
||||
{
|
||||
if (histPos == L-1)
|
||||
{
|
||||
// Save this entry in case the user hits the down key.
|
||||
histList[histPos] = _in.value;
|
||||
}
|
||||
|
||||
if (histPos > 0)
|
||||
{
|
||||
histPos--;
|
||||
// Use a timeout to prevent up from moving cursor within new text
|
||||
// Set to nothing first for the same reason
|
||||
setTimeout(
|
||||
function() {
|
||||
_in.value = '';
|
||||
_in.value = histList[histPos];
|
||||
var caretPos = _in.value.length;
|
||||
if (_in.setSelectionRange)
|
||||
_in.setSelectionRange(caretPos, caretPos);
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
else // down
|
||||
{
|
||||
if (histPos < L-1)
|
||||
{
|
||||
histPos++;
|
||||
_in.value = histList[histPos];
|
||||
}
|
||||
else if (histPos == L-1)
|
||||
{
|
||||
// Already on the current entry: clear but save
|
||||
if (_in.value)
|
||||
{
|
||||
histList[histPos] = _in.value;
|
||||
++histPos;
|
||||
_in.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tabcomplete()
|
||||
{
|
||||
/*
|
||||
* Working backwards from s[from], find the spot
|
||||
* where this expression starts. It will scan
|
||||
* until it hits a mismatched ( or a space,
|
||||
* but it skips over quoted strings.
|
||||
* If stopAtDot is true, stop at a '.'
|
||||
*/
|
||||
function findbeginning(s, from, stopAtDot)
|
||||
{
|
||||
/*
|
||||
* Complicated function.
|
||||
*
|
||||
* Return true if s[i] == q BUT ONLY IF
|
||||
* s[i-1] is not a backslash.
|
||||
*/
|
||||
function equalButNotEscaped(s,i,q)
|
||||
{
|
||||
if(s.charAt(i) != q) // not equal go no further
|
||||
return false;
|
||||
|
||||
if(i==0) // beginning of string
|
||||
return true;
|
||||
|
||||
if(s.charAt(i-1) == '\\') // escaped?
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var nparens = 0;
|
||||
var i;
|
||||
for(i=from; i>=0; i--)
|
||||
{
|
||||
if(s.charAt(i) == ' ')
|
||||
break;
|
||||
|
||||
if(stopAtDot && s.charAt(i) == '.')
|
||||
break;
|
||||
|
||||
if(s.charAt(i) == ')')
|
||||
nparens++;
|
||||
else if(s.charAt(i) == '(')
|
||||
nparens--;
|
||||
|
||||
if(nparens < 0)
|
||||
break;
|
||||
|
||||
// skip quoted strings
|
||||
if(s.charAt(i) == '\'' || s.charAt(i) == '\"')
|
||||
{
|
||||
//dump("skipping quoted chars: ");
|
||||
var quot = s.charAt(i);
|
||||
i--;
|
||||
while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
|
||||
//dump(s.charAt(i));
|
||||
i--;
|
||||
}
|
||||
//dump("\n");
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code)
|
||||
// XXX doesn't work in IE, even though it contains IE-specific code
|
||||
function getcaretpos(inp)
|
||||
{
|
||||
if(inp.selectionEnd != null)
|
||||
return inp.selectionEnd;
|
||||
|
||||
if(inp.createTextRange)
|
||||
{
|
||||
var docrange = _win.Shell.document.selection.createRange();
|
||||
var inprange = inp.createTextRange();
|
||||
if (inprange.setEndPoint)
|
||||
{
|
||||
inprange.setEndPoint('EndToStart', docrange);
|
||||
return inprange.text.length;
|
||||
}
|
||||
}
|
||||
|
||||
return inp.value.length; // sucks, punt
|
||||
}
|
||||
|
||||
function setselectionto(inp,pos)
|
||||
{
|
||||
if(inp.selectionStart) {
|
||||
inp.selectionStart = inp.selectionEnd = pos;
|
||||
}
|
||||
else if(inp.createTextRange) {
|
||||
var docrange = _win.Shell.document.selection.createRange();
|
||||
var inprange = inp.createTextRange();
|
||||
inprange.move('character',pos);
|
||||
inprange.select();
|
||||
}
|
||||
else { // err...
|
||||
/*
|
||||
inp.select();
|
||||
if(_win.Shell.document.getSelection())
|
||||
_win.Shell.document.getSelection() = "";
|
||||
*/
|
||||
}
|
||||
}
|
||||
// get position of cursor within the input box
|
||||
var caret = getcaretpos(_in);
|
||||
|
||||
if(caret) {
|
||||
//dump("----\n");
|
||||
var dotpos, spacepos, complete, obj;
|
||||
//dump("caret pos: " + caret + "\n");
|
||||
// see if there's a dot before here
|
||||
dotpos = findbeginning(_in.value, caret-1, true);
|
||||
//dump("dot pos: " + dotpos + "\n");
|
||||
if(dotpos == -1 || _in.value.charAt(dotpos) != '.') {
|
||||
dotpos = caret;
|
||||
//dump("changed dot pos: " + dotpos + "\n");
|
||||
}
|
||||
|
||||
// look backwards for a non-variable-name character
|
||||
spacepos = findbeginning(_in.value, dotpos-1, false);
|
||||
//dump("space pos: " + spacepos + "\n");
|
||||
// get the object we're trying to complete on
|
||||
if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret)
|
||||
{
|
||||
// try completing function args
|
||||
if(_in.value.charAt(dotpos) == '(' ||
|
||||
(_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))
|
||||
{
|
||||
var fn,fname;
|
||||
var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos;
|
||||
spacepos = findbeginning(_in.value, from-1, false);
|
||||
|
||||
fname = _in.value.substr(spacepos+1,from-(spacepos+1));
|
||||
//dump("fname: " + fname + "\n");
|
||||
try {
|
||||
with(_win.Shell._scope)
|
||||
with(_win)
|
||||
with(Shell.shellCommands)
|
||||
fn = eval(fname);
|
||||
}
|
||||
catch(er) {
|
||||
//dump('fn is not a valid object\n');
|
||||
return;
|
||||
}
|
||||
if(fn == undefined) {
|
||||
//dump('fn is undefined');
|
||||
return;
|
||||
}
|
||||
if(fn instanceof Function)
|
||||
{
|
||||
// Print function definition, including argument names, but not function body
|
||||
if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/))
|
||||
println(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
obj = _win;
|
||||
}
|
||||
else
|
||||
{
|
||||
var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
|
||||
//dump("objname: |" + objname + "|\n");
|
||||
try {
|
||||
with(_win.Shell._scope)
|
||||
with(_win)
|
||||
obj = eval(objname);
|
||||
}
|
||||
catch(er) {
|
||||
printError(er);
|
||||
return;
|
||||
}
|
||||
if(obj == undefined) {
|
||||
// sometimes this is tabcomplete's fault, so don't print it :(
|
||||
// e.g. completing from "print(document.getElements"
|
||||
// println("Can't complete from null or undefined expression " + objname, "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
//dump("obj: " + obj + "\n");
|
||||
// get the thing we're trying to complete
|
||||
if(dotpos == caret)
|
||||
{
|
||||
if(spacepos+1 == dotpos || spacepos == dotpos)
|
||||
{
|
||||
// nothing to complete
|
||||
//dump("nothing to complete\n");
|
||||
return;
|
||||
}
|
||||
|
||||
complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
|
||||
}
|
||||
else {
|
||||
complete = _in.value.substr(dotpos+1,caret-(dotpos+1));
|
||||
}
|
||||
//dump("complete: " + complete + "\n");
|
||||
// ok, now look at all the props/methods of this obj
|
||||
// and find ones starting with 'complete'
|
||||
var matches = [];
|
||||
var bestmatch = null;
|
||||
for(var a in obj)
|
||||
{
|
||||
//a = a.toString();
|
||||
//XXX: making it lowercase could help some cases,
|
||||
// but screws up my general logic.
|
||||
if(a.substr(0,complete.length) == complete) {
|
||||
matches.push(a);
|
||||
////dump("match: " + a + "\n");
|
||||
// if no best match, this is the best match
|
||||
if(bestmatch == null)
|
||||
{
|
||||
bestmatch = a;
|
||||
}
|
||||
else {
|
||||
// the best match is the longest common string
|
||||
function min(a,b){ return ((a<b)?a:b); }
|
||||
var i;
|
||||
for(i=0; i< min(bestmatch.length, a.length); i++)
|
||||
{
|
||||
if(bestmatch.charAt(i) != a.charAt(i))
|
||||
break;
|
||||
}
|
||||
bestmatch = bestmatch.substr(0,i);
|
||||
////dump("bestmatch len: " + i + "\n");
|
||||
}
|
||||
////dump("bestmatch: " + bestmatch + "\n");
|
||||
}
|
||||
}
|
||||
bestmatch = (bestmatch || "");
|
||||
////dump("matches: " + matches + "\n");
|
||||
var objAndComplete = (objname || obj) + "." + bestmatch;
|
||||
//dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n");
|
||||
if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) {
|
||||
|
||||
printWithRunin("Matches: ", matches.join(', '), "tabcomplete");
|
||||
tooManyMatches = null;
|
||||
}
|
||||
else if(matches.length > 10)
|
||||
{
|
||||
println(matches.length + " matches. Press tab again to see them all", "tabcomplete");
|
||||
tooManyMatches = objAndComplete;
|
||||
}
|
||||
else {
|
||||
tooManyMatches = null;
|
||||
}
|
||||
if(bestmatch != "")
|
||||
{
|
||||
var sstart;
|
||||
if(dotpos == caret) {
|
||||
sstart = spacepos+1;
|
||||
}
|
||||
else {
|
||||
sstart = dotpos+1;
|
||||
}
|
||||
_in.value = _in.value.substr(0, sstart)
|
||||
+ bestmatch
|
||||
+ _in.value.substr(caret);
|
||||
setselectionto(_in,caret + (bestmatch.length - complete.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printQuestion(q)
|
||||
{
|
||||
println(q, "input");
|
||||
}
|
||||
|
||||
function printAnswer(a)
|
||||
{
|
||||
if (a !== undefined) {
|
||||
println(a, "normalOutput");
|
||||
shellCommands.ans = a;
|
||||
}
|
||||
}
|
||||
|
||||
function printError(er)
|
||||
{
|
||||
var lineNumberString;
|
||||
|
||||
lastError = er; // for debugging the shell
|
||||
if (er.name)
|
||||
{
|
||||
// lineNumberString should not be "", to avoid a very wacky bug in IE 6.
|
||||
lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": ";
|
||||
println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString.
|
||||
}
|
||||
else
|
||||
println(er, "error"); // Because security errors in Moz /only/ have toString.
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates |s| or current input (_in.value) in the previously set up context.
|
||||
* @param {String} s - (optional) command to evaluate.
|
||||
*/
|
||||
function go(s)
|
||||
{
|
||||
// save the command to eval in |question|, so that the target window can access
|
||||
// it when evaluating.
|
||||
_in.value = question = s ? s : _in.value;
|
||||
|
||||
if (question == "")
|
||||
return;
|
||||
|
||||
histList[histList.length-1] = question;
|
||||
histList[histList.length] = "";
|
||||
histPos = histList.length - 1;
|
||||
|
||||
// Unfortunately, this has to happen *before* the JavaScript is run, so that
|
||||
// print() output will go in the right place.
|
||||
_in.value='';
|
||||
recalculateInputHeight();
|
||||
printQuestion(question);
|
||||
|
||||
if (_win.closed) {
|
||||
printError("Target window has been closed.");
|
||||
return;
|
||||
}
|
||||
|
||||
try { ("Shell" in _win) }
|
||||
catch(er) {
|
||||
printError("The JavaScript Shell cannot access variables in the target window. The most likely reason is that the target window now has a different page loaded and that page has a different hostname than the original page.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("Shell" in _win))
|
||||
initTarget(); // silent
|
||||
|
||||
// Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC).
|
||||
run(_win, "try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) {' + Shell.question + String.fromCharCode(10) + '}')); } catch(er) { Shell.printError(er); }; setTimeout(Shell.refocus, 0);");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- for http://ted.mielczarek.org/code/mozilla/extensiondev/ -->
|
||||
<script type="text/javascript" src="chrome://extensiondev/content/chromeShellExtras.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body { background: white; color: black; }
|
||||
|
||||
#output {
|
||||
/* Preserve line breaks, but wrap too if browser supports it */
|
||||
white-space: pre;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
h3 { margin-top: 0; margin-bottom: 0em; }
|
||||
h3 + div { margin: 0; }
|
||||
|
||||
form { margin: 0; padding: 0; }
|
||||
#input { width: 100%; border: none; padding: 0; overflow: auto; }
|
||||
|
||||
.input { color: blue; background: white; font: inherit; font-weight: bold; margin-top: .5em; /* background: #E6E6FF; */ }
|
||||
.normalOutput { color: black; background: white; }
|
||||
.print { color: brown; background: white; }
|
||||
.error { color: red; background: white; }
|
||||
.propList { color: green; background: white; }
|
||||
.message { color: green; background: white; }
|
||||
.tabcomplete { color: purple; background: white; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body onload="init()">
|
||||
|
||||
<div id="output"><h3>JavaScript Shell 1.4</h3><div>Features: autocompletion of property names with Tab, multiline input with Shift+Enter, input history with (Ctrl+) Up/Down, <a accesskey="M" href="javascript:go('scope(Math); mathHelp();');" title="Accesskey: M">Math</a>, <a accesskey="H" href="http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.4" title="Accesskey: H">help</a></div><div>Values and functions: ans, print(string), <a accesskey="P" href="javascript:go('props(ans)')" title="Accesskey: P">props(object)</a>, <a accesskey="B" href="javascript:go('blink(ans)')" title="Accesskey: B">blink(node)</a>, <a accesskey="C" href="javascript:go('clear()')" title="Accesskey: C">clear()</a>, load(scriptURL), scope(object)</div></div>
|
||||
|
||||
<div><textarea id="input" class="input" wrap="off" spellcheck="false" onkeydown="inputKeydown(event)" rows="1"></textarea></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
|
||||
<window id="jsshell"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
contenttitlesetting="true"
|
||||
title="Debug JSShell"
|
||||
width="600"
|
||||
height="400"
|
||||
persist="width height screenX screenY">
|
||||
<browser id="content" flex="1" type="content-primary" src="chrome://browser/content/shell.html"/>
|
||||
</window>
|
|
@ -0,0 +1,241 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % certerrorDTD
|
||||
SYSTEM "chrome://browser/locale/aboutCertError.dtd">
|
||||
%certerrorDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&certerror.pagetitle;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
|
||||
<!-- This page currently uses the same favicon as neterror.xhtml.
|
||||
If the location of the favicon is changed for both pages, the
|
||||
FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
|
||||
should be updated. If this page starts using a different favicon
|
||||
than neterror, nsFaviconService->SetAndFetchFaviconForPage
|
||||
should be updated to ignore this one as well. -->
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:certerror?e=error&u=url&d=desc
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
if (!matches || matches.length < 2)
|
||||
return "";
|
||||
|
||||
// parenthetical match is the second entry
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var desc = url.search(/d\=/);
|
||||
|
||||
// desc == -1 if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (desc == -1)
|
||||
return "";
|
||||
|
||||
return decodeURIComponent(url.slice(desc + 2));
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
// Replace the "#1" string in the intro with the hostname. Trickier
|
||||
// than it might seem since we want to preserve the <b> tags, but
|
||||
// not allow for any injection by just using innerHTML. Instead,
|
||||
// just find the right target text node.
|
||||
var intro = document.getElementById('introContentP1');
|
||||
function replaceWithHost(node) {
|
||||
if (node.textContent == "#1")
|
||||
node.textContent = location.host;
|
||||
else
|
||||
for(var i = 0; i < node.childNodes.length; i++)
|
||||
replaceWithHost(node.childNodes[i]);
|
||||
};
|
||||
replaceWithHost(intro);
|
||||
|
||||
if (getCSSClass() == "expertBadCert") {
|
||||
toggle('technicalContent');
|
||||
toggle('expertContent');
|
||||
}
|
||||
|
||||
var tech = document.getElementById("technicalContentText");
|
||||
if (tech)
|
||||
tech.textContent = getDescription();
|
||||
|
||||
addDomainErrorLink();
|
||||
}
|
||||
|
||||
/* In the case of SSL error pages about domain mismatch, see if
|
||||
we can hyperlink the user to the correct site. We don't want
|
||||
to do this generically since it allows MitM attacks to redirect
|
||||
users to a site under attacker control, but in certain cases
|
||||
it is safe (and helpful!) to do so. Bug 402210
|
||||
*/
|
||||
function addDomainErrorLink() {
|
||||
// Rather than textContent, we need to treat description as HTML
|
||||
var sd = document.getElementById("technicalContentText");
|
||||
if (sd) {
|
||||
var desc = getDescription();
|
||||
|
||||
// sanitize description text - see bug 441169
|
||||
|
||||
// First, find the index of the <a> tag we care about, being careful not to
|
||||
// use an over-greedy regex
|
||||
var re = /<a id="cert_domain_link" title="([^"]+)">/;
|
||||
var result = re.exec(desc);
|
||||
if(!result)
|
||||
return;
|
||||
|
||||
// Remove sd's existing children
|
||||
sd.textContent = "";
|
||||
|
||||
// Everything up to the link should be text content
|
||||
sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
|
||||
|
||||
// Now create the link itself
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", "cert_domain_link");
|
||||
anchorEl.setAttribute("title", result[1]);
|
||||
anchorEl.appendChild(document.createTextNode(result[1]));
|
||||
sd.appendChild(anchorEl);
|
||||
|
||||
// Finally, append text for anything after the closing </a>
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
var link = document.getElementById('cert_domain_link');
|
||||
if (!link)
|
||||
return;
|
||||
|
||||
var okHost = link.getAttribute("title");
|
||||
var thisHost = document.location.hostname;
|
||||
var proto = document.location.protocol;
|
||||
|
||||
// If okHost is a wildcard domain ("*.example.com") let's
|
||||
// use "www" instead. "*.example.com" isn't going to
|
||||
// get anyone anywhere useful. bug 432491
|
||||
okHost = okHost.replace(/^\*\./, "www.");
|
||||
|
||||
/* case #1:
|
||||
* example.com uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for www.example.com
|
||||
*
|
||||
* Make sure to include the "." ahead of thisHost so that
|
||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
||||
*
|
||||
* We'd normally just use a RegExp here except that we lack a
|
||||
* library function to escape them properly (bug 248062), and
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (endsWith(okHost, "." + thisHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
/* case #2:
|
||||
* browser.garage.maemo.org uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (endsWith(thisHost, "." + okHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (link.href && getCSSClass() != "expertBadCert")
|
||||
toggle("technicalContent");
|
||||
}
|
||||
|
||||
function endsWith(haystack, needle) {
|
||||
return haystack.slice(-needle.length) == needle;
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el.getAttribute("collapsed"))
|
||||
el.setAttribute("collapsed", false);
|
||||
else
|
||||
el.setAttribute("collapsed", true);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" class="certerror" dir="&locale.dir;">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 class="errorTitleText">&certerror.longpagetitle;</h1>
|
||||
</div>
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
<div id="introContent">
|
||||
<p id="introContentP1">&certerror.introPara1;</p>
|
||||
</div>
|
||||
|
||||
<div id="whatShouldIDoContent">
|
||||
<h2>&certerror.whatShouldIDo.heading;</h2>
|
||||
<div id="whatShouldIDoContentText">
|
||||
<p>&certerror.whatShouldIDo.content;</p>
|
||||
<xul:button xmlns:xul='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' id='getMeOutOfHereButton' label='&certerror.getMeOutOfHere.label;'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The following sections can be unhidden by default by setting the
|
||||
"browser.xul.error_pages.expert_bad_cert" pref to true -->
|
||||
<div id="technicalContent" collapsed="true">
|
||||
<h2 onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2>
|
||||
<p id="technicalContentText"/>
|
||||
</div>
|
||||
|
||||
<div id="expertContent" collapsed="true">
|
||||
<h2 onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2>
|
||||
<div>
|
||||
<p>&certerror.expert.content;</p>
|
||||
<p>&certerror.expert.contentPara2;</p>
|
||||
<xul:button xmlns:xul='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' id='temporaryExceptionButton' label='&certerror.addTemporaryException.label;'/>
|
||||
<xul:button xmlns:xul='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' id='permanentExceptionButton' label='&certerror.addPermanentException.label;'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
|
||||
%aboutRightsDTD;
|
||||
]>
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>Oh noooz! You crashed!</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
|
||||
|
||||
<h1>Oh noooz! You crashed!</h1>
|
||||
|
||||
<p>A crash report is being submitted as you read this, we hope! Check <a href="about:crashes">about:crashes</a> for crash reports.</p>
|
||||
<p>(I'm know I'm boring to look at, hopefully someday I'll be pretty and useful and stuff!)</p>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
]]></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
|
||||
%aboutRightsDTD;
|
||||
]>
|
||||
# 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/.
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>&rights.pagetitle;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
|
||||
|
||||
<h1>&rights.intro-header;</h1>
|
||||
|
||||
<p>&rights.intro;</p>
|
||||
|
||||
<ul>
|
||||
<li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li>
|
||||
# Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
|
||||
# Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
|
||||
# Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace)
|
||||
<li>&rights.intro-point2-a;<a href="http://www.mozilla.org/foundation/trademarks/policy.html">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li>
|
||||
<li>&rights.intro-point2.5;</li>
|
||||
<li>&rights2.intro-point3a;<a href="http://www.mozilla.com/legal/privacy/">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
|
||||
<li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li>
|
||||
</ul>
|
||||
|
||||
<div id="webservices-container">
|
||||
<a name="webservices"/>
|
||||
<h3>&rights2.webservices-header;</h3>
|
||||
|
||||
<p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights2.webservices-c;</p>
|
||||
|
||||
<div id="disabling-webservices-container" style="margin-left:40px;">
|
||||
<a name="disabling-webservices"/>
|
||||
<!-- XXX Safe Browsing is not enabled in Firefox Mobile -->
|
||||
<!--
|
||||
<p><strong>&rights.safebrowsing-a;</strong>&rights.safebrowsing-b;</p>
|
||||
<ul>
|
||||
<li>&rights.safebrowsing-term1;</li>
|
||||
<li>&rights.safebrowsing-term2;</li>
|
||||
<li>&rights.safebrowsing-term3;</li>
|
||||
<li>&rights.safebrowsing-term4;</li>
|
||||
</ul>
|
||||
-->
|
||||
|
||||
<p><strong>&rights.locationawarebrowsing-a;</strong>&rights.locationawarebrowsing-b;</p>
|
||||
<ul>
|
||||
<li>&rights.locationawarebrowsing-term1a;<code>&rights.locationawarebrowsing-term1b;</code></li>
|
||||
<li>&rights.locationawarebrowsing-term2;</li>
|
||||
<li>&rights.locationawarebrowsing-term3;</li>
|
||||
<li>&rights.locationawarebrowsing-term4;</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ol>
|
||||
# Terms only apply to official builds, unbranded builds get a placeholder.
|
||||
<li>&rights2.webservices-term1;</li>
|
||||
<li>&rights.webservices-term2;</li>
|
||||
<li>&rights2.webservices-term3;</li>
|
||||
<li><strong>&rights.webservices-term4;</strong></li>
|
||||
<li><strong>&rights.webservices-term5;</strong></li>
|
||||
<li>&rights.webservices-term6;</li>
|
||||
<li>&rights.webservices-term7;</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
var servicesDiv = document.getElementById("webservices-container");
|
||||
servicesDiv.style.display = "none";
|
||||
|
||||
function showServices() {
|
||||
servicesDiv.style.display = "";
|
||||
}
|
||||
|
||||
var disablingServicesDiv = document.getElementById("disabling-webservices-container");
|
||||
disablingServicesDiv.style.display = "none";
|
||||
|
||||
function showDisablingServices() {
|
||||
disablingServicesDiv.style.display = "";
|
||||
}
|
||||
]]></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,192 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/phishing.dtd">
|
||||
%blockedSiteDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:blocked?e=error_code&u=url
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getURL()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var match = url.match(/&u=([^&]+)&/);
|
||||
|
||||
// match == null if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (!match)
|
||||
return "";
|
||||
|
||||
url = decodeURIComponent(match[1]);
|
||||
|
||||
// If this is a view-source page, then get then real URI of the page
|
||||
if (/^view-source\:/.test(url))
|
||||
url = url.slice(12);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the hostname via document.location. Fail back
|
||||
* to getURL so that we always return something meaningful.
|
||||
*/
|
||||
function getHostString()
|
||||
{
|
||||
try {
|
||||
return document.location.hostname;
|
||||
} catch (e) {
|
||||
return getURL();
|
||||
}
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
// Handoff to the appropriate initializer, based on error code
|
||||
switch (getErrorCode()) {
|
||||
case "malwareBlocked" :
|
||||
initPage_malware();
|
||||
break;
|
||||
case "phishingBlocked" :
|
||||
initPage_phishing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize custom strings and functionality for blocked malware case
|
||||
*/
|
||||
function initPage_malware()
|
||||
{
|
||||
// Remove phishing strings
|
||||
var el = document.getElementById("errorTitleText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
el = document.getElementById("errorShortDescText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
el = document.getElementById("errorLongDescText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
// Set sitename
|
||||
document.getElementById("malware_sitename").textContent = getHostString();
|
||||
document.title = document.getElementById("errorTitleText_malware")
|
||||
.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize custom strings and functionality for blocked phishing case
|
||||
*/
|
||||
function initPage_phishing()
|
||||
{
|
||||
// Remove malware strings
|
||||
var el = document.getElementById("errorTitleText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
el = document.getElementById("errorShortDescText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
el = document.getElementById("errorLongDescText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
|
||||
// Set sitename
|
||||
document.getElementById("phishing_sitename").textContent = getHostString();
|
||||
document.title = document.getElementById("errorTitleText_phishing")
|
||||
.innerHTML;
|
||||
}
|
||||
]]></script>
|
||||
<style type="text/css">
|
||||
/* Style warning button to look like a small text link in the
|
||||
bottom right. This is preferable to just using a text link
|
||||
since there is already a mechanism in browser.js for trapping
|
||||
oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
|
||||
#ignoreWarningButton {
|
||||
-moz-appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white; /* Hard coded because netError.css forces this page's background to dark red */
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
top: 23px;
|
||||
left: 20px;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#ignoreWarning {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" class="blockedsite" dir="&locale.dir;">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title2;</h1>
|
||||
<h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
|
||||
</div>
|
||||
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p>
|
||||
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description -->
|
||||
<div id="errorLongDesc">
|
||||
<p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
|
||||
<p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div id="buttons">
|
||||
<!-- Commands handled in browser.js -->
|
||||
<button id="getMeOutButton">&safeb.palm.accept.label;</button>
|
||||
<button id="reportButton">&safeb.palm.reportPage.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,154 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
# 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/.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" accelerated="11">
|
||||
<head>
|
||||
|
||||
<style type="text/css"><![CDATA[
|
||||
html,
|
||||
body,
|
||||
video {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: black;
|
||||
overflow: -moz-hidden-unscrollable;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
]]></style>
|
||||
|
||||
<script type="application/javascript;version=1.8"><![CDATA[
|
||||
|
||||
var TheaterTab = {
|
||||
videoElement: null,
|
||||
idleTimer: 0,
|
||||
|
||||
init: function init() {
|
||||
this.videoElement = document.querySelector("video");
|
||||
|
||||
/*
|
||||
* video events
|
||||
*/
|
||||
|
||||
this.videoElement.addEventListener("loadeddata", TheaterTab.loadDataCallback, false);
|
||||
this.videoElement.addEventListener("seeked", TheaterTab.seekComplete, false);
|
||||
this.videoElement.addEventListener("pause", TheaterTab.pauseCallback, false);
|
||||
this.videoElement.addEventListener("play", TheaterTab.playCallback, false);
|
||||
this.videoElement.addEventListener("ended", TheaterTab.endedCallback, false);
|
||||
|
||||
/*
|
||||
* window events
|
||||
*/
|
||||
|
||||
window.addEventListener("click", function () {
|
||||
TheaterTab.togglePlay();
|
||||
TheaterTab.resetIdleTimer();
|
||||
}, false);
|
||||
|
||||
window.addEventListener("unload", function () {
|
||||
if (TheaterTab.videoElement.currentSrc) {
|
||||
contentVideo.currentTime = TheaterTab.videoElement.currentTime;
|
||||
contentVideo.volume = TheaterTab.videoElement.volume;
|
||||
contentVideo.muted = TheaterTab.videoElement.muted;
|
||||
if (!TheaterTab.videoElement.paused && !TheaterTab.videoElement.ended) {
|
||||
TheaterTab.videoElement.pause();
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
window.addEventListener("keypress", function (event) {
|
||||
TheaterTab.resetIdleTimer();
|
||||
}, false);
|
||||
|
||||
|
||||
// Load the video up and play it
|
||||
this.videoElement.mozLoadFrom(contentVideo);
|
||||
},
|
||||
|
||||
/*
|
||||
* Video element callbacks
|
||||
*/
|
||||
|
||||
loadDataCallback: function loadDataCallback() {
|
||||
dump("loadDataCallback()\n");
|
||||
TheaterTab.videoElement.removeEventListener("loadeddata", arguments.callee, false);
|
||||
TheaterTab.videoElement.volume = contentVideo.volume;
|
||||
TheaterTab.videoElement.muted = contentVideo.muted;
|
||||
TheaterTab.videoElement.poster = contentVideo.poster;
|
||||
|
||||
// If we are starting from mid stream, wait until we have
|
||||
// seeked to the start location.
|
||||
dump(contentVideo + "\n");
|
||||
if (contentVideo.currentTime && !contentVideo.ended) {
|
||||
// set up callback to play
|
||||
TheaterTab.videoElement.addEventListener("seeked", function () {
|
||||
TheaterTab.videoElement.removeEventListener("seeked", arguments.callee, false);
|
||||
TheaterTab.seekComplete();
|
||||
}, false);
|
||||
// seek
|
||||
TheaterTab.videoElement.currentTime = contentVideo.currentTime;
|
||||
return;
|
||||
}
|
||||
TheaterTab.play();
|
||||
},
|
||||
|
||||
seekComplete: function seekComplete() {
|
||||
TheaterTab.play();
|
||||
},
|
||||
|
||||
pauseCallback: function pauseCallback() {
|
||||
},
|
||||
|
||||
playCallback: function playCallback() {
|
||||
},
|
||||
|
||||
endedCallback: function endedCallback() {
|
||||
},
|
||||
|
||||
/*
|
||||
* Video control
|
||||
*/
|
||||
|
||||
play: function play() {
|
||||
this.videoElement.play();
|
||||
},
|
||||
|
||||
pause: function pause() {
|
||||
this.videoElement.pause();
|
||||
},
|
||||
|
||||
togglePlay: function togglePlay() {
|
||||
if (this.videoElement.paused) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
Components.utils.import("resource:///modules/video.jsm");
|
||||
|
||||
// The video in the content tab we launched from
|
||||
var contentVideo = Video.fullScreenSourceElement;
|
||||
|
||||
// ??
|
||||
Video.fullScreenSourceElement = null;
|
||||
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body onload="TheaterTab.init();">
|
||||
<video controls="true"/>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,365 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false;" />
|
||||
<title>&loadError.label;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
|
||||
<!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
|
||||
toolkit/components/places/src/nsFaviconService.h should be updated. -->
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// moz-neterror:page?e=error&u=url&d=desc
|
||||
//
|
||||
// or optionally, to specify an alternate CSS class to allow for
|
||||
// custom styling and favicon:
|
||||
//
|
||||
// moz-neterror:page?e=error&u=url&s=classname&d=desc
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
if (!matches || matches.length < 2)
|
||||
return "";
|
||||
|
||||
// parenthetical match is the second entry
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var desc = url.search(/d\=/);
|
||||
|
||||
// desc == -1 if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (desc == -1)
|
||||
return "";
|
||||
|
||||
return decodeURIComponent(url.slice(desc + 2));
|
||||
}
|
||||
|
||||
function retryThis(buttonEl)
|
||||
{
|
||||
// Note: The application may wish to handle switching off "offline mode"
|
||||
// before this event handler runs, but using a capturing event handler.
|
||||
|
||||
// Session history has the URL of the page that failed
|
||||
// to load, not the one of the error page. So, just call
|
||||
// reload(), which will also repost POST data correctly.
|
||||
try {
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
// We probably tried to reload a URI that caused an exception to
|
||||
// occur; e.g. a nonexistent file.
|
||||
}
|
||||
|
||||
buttonEl.disabled = true;
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
var err = getErrorCode();
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc)
|
||||
{
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
var title = document.getElementsByClassName("errorTitleText")[0];
|
||||
if (title)
|
||||
{
|
||||
title.parentNode.replaceChild(errTitle, title);
|
||||
// change id to the replaced child's id so styling works
|
||||
errTitle.classList.add("errorTitleText");
|
||||
}
|
||||
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd)
|
||||
sd.textContent = getDescription();
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
{
|
||||
ld.parentNode.replaceChild(errDesc, ld);
|
||||
// change id to the replaced child's id so styling works
|
||||
errDesc.id = "errorLongDesc";
|
||||
}
|
||||
|
||||
// remove undisplayed errors to avoid bug 39098
|
||||
var errContainer = document.getElementById("errorContainer");
|
||||
errContainer.parentNode.removeChild(errContainer);
|
||||
|
||||
var className = getCSSClass();
|
||||
if (className && className != "expertBadCert") {
|
||||
// Associate a CSS class with the root of the page, if one was passed in,
|
||||
// to allow custom styling.
|
||||
// Not "expertBadCert" though, don't want to deal with the favicon
|
||||
document.documentElement.className = className;
|
||||
|
||||
// Also, if they specified a CSS class, they must supply their own
|
||||
// favicon. In order to trigger the browser to repaint though, we
|
||||
// need to remove/add the link element.
|
||||
var favicon = document.getElementById("favicon");
|
||||
var faviconParent = favicon.parentNode;
|
||||
faviconParent.removeChild(favicon);
|
||||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
||||
faviconParent.appendChild(favicon);
|
||||
}
|
||||
if (className == "expertBadCert") {
|
||||
showSecuritySection();
|
||||
}
|
||||
|
||||
if (err == "remoteXUL") {
|
||||
// Remove the "Try again" button for remote XUL errors given that
|
||||
// it is useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "cspFrameAncestorBlocked") {
|
||||
// Remove the "Try again" button for CSP frame ancestors violation, since it's
|
||||
// almost certainly useless. (Bug 553180)
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "nssBadCert") {
|
||||
// Remove the "Try again" button for security exceptions, since it's
|
||||
// almost certainly useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
document.getElementById("errorPage").setAttribute("class", "certerror");
|
||||
addDomainErrorLink();
|
||||
}
|
||||
else {
|
||||
// Remove the override block for non-certificate errors. CSS-hiding
|
||||
// isn't good enough here, because of bug 39098
|
||||
var secOverride = document.getElementById("securityOverrideDiv");
|
||||
secOverride.parentNode.removeChild(secOverride);
|
||||
}
|
||||
}
|
||||
|
||||
function showSecuritySection() {
|
||||
// Swap link out, content in
|
||||
document.getElementById('securityOverrideContent').style.display = '';
|
||||
document.getElementById('securityOverrideLink').style.display = 'none';
|
||||
}
|
||||
|
||||
/* In the case of SSL error pages about domain mismatch, see if
|
||||
we can hyperlink the user to the correct site. We don't want
|
||||
to do this generically since it allows MitM attacks to redirect
|
||||
users to a site under attacker control, but in certain cases
|
||||
it is safe (and helpful!) to do so. Bug 402210
|
||||
*/
|
||||
function addDomainErrorLink() {
|
||||
// Rather than textContent, we need to treat description as HTML
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd) {
|
||||
var desc = getDescription();
|
||||
|
||||
// sanitize description text - see bug 441169
|
||||
|
||||
// First, find the index of the <a> tag we care about, being careful not to
|
||||
// use an over-greedy regex
|
||||
var re = /<a id="cert_domain_link" title="([^"]+)">/;
|
||||
var result = re.exec(desc);
|
||||
if(!result)
|
||||
return;
|
||||
|
||||
// Remove sd's existing children
|
||||
sd.textContent = "";
|
||||
|
||||
// Everything up to the link should be text content
|
||||
sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
|
||||
|
||||
// Now create the link itself
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", "cert_domain_link");
|
||||
anchorEl.setAttribute("title", result[1]);
|
||||
anchorEl.appendChild(document.createTextNode(result[1]));
|
||||
sd.appendChild(anchorEl);
|
||||
|
||||
// Finally, append text for anything after the closing </a>
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
var link = document.getElementById('cert_domain_link');
|
||||
if (!link)
|
||||
return;
|
||||
|
||||
var okHost = link.getAttribute("title");
|
||||
var thisHost = document.location.hostname;
|
||||
var proto = document.location.protocol;
|
||||
|
||||
// If okHost is a wildcard domain ("*.example.com") let's
|
||||
// use "www" instead. "*.example.com" isn't going to
|
||||
// get anyone anywhere useful. bug 432491
|
||||
okHost = okHost.replace(/^\*\./, "www.");
|
||||
|
||||
/* case #1:
|
||||
* example.com uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for www.example.com
|
||||
*
|
||||
* Make sure to include the "." ahead of thisHost so that
|
||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
||||
*
|
||||
* We'd normally just use a RegExp here except that we lack a
|
||||
* library function to escape them properly (bug 248062), and
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (endsWith(okHost, "." + thisHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
/* case #2:
|
||||
* browser.garage.maemo.org uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (endsWith(thisHost, "." + okHost))
|
||||
link.href = proto + okHost;
|
||||
}
|
||||
|
||||
function endsWith(haystack, needle) {
|
||||
return haystack.slice(-needle.length) == needle;
|
||||
}
|
||||
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" dir="&locale.dir;">
|
||||
|
||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
||||
<div id="errorContainer">
|
||||
<div id="errorTitlesContainer">
|
||||
<h1 id="et_generic">&generic.title;</h1>
|
||||
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
|
||||
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
|
||||
<h1 id="et_malformedURI">&malformedURI.title;</h1>
|
||||
<h1 id="et_protocolNotFound">&protocolNotFound.title;</h1>
|
||||
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
|
||||
<h1 id="et_netTimeout">&netTimeout.title;</h1>
|
||||
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
|
||||
<h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
|
||||
<h1 id="et_netReset">&netReset.title;</h1>
|
||||
<h1 id="et_notCached">¬Cached.title;</h1>
|
||||
<h1 id="et_netOffline">&netOffline.title;</h1>
|
||||
<h1 id="et_netInterrupt">&netInterrupt.title;</h1>
|
||||
<h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
|
||||
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
|
||||
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
|
||||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
|
||||
<h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
|
||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
||||
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
|
||||
</div>
|
||||
<div id="errorDescriptionsContainer">
|
||||
<div id="ed_generic">&generic.longDesc;</div>
|
||||
<div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
|
||||
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
|
||||
<div id="ed_malformedURI">&malformedURI.longDesc;</div>
|
||||
<div id="ed_protocolNotFound">&protocolNotFound.longDesc;</div>
|
||||
<div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
|
||||
<div id="ed_netTimeout">&netTimeout.longDesc;</div>
|
||||
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
|
||||
<div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
|
||||
<div id="ed_netReset">&netReset.longDesc;</div>
|
||||
<div id="ed_notCached">¬Cached.longDesc;</div>
|
||||
<div id="ed_netOffline">&netOffline.longDesc2;</div>
|
||||
<div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
|
||||
<div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
|
||||
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
|
||||
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
|
||||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
||||
<div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
|
||||
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
|
||||
<div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
|
||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
||||
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 class="errorTitleText" />
|
||||
</div>
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText" />
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc" />
|
||||
|
||||
<!-- Override section - For ssl errors only. Removed on init for other
|
||||
error types. -->
|
||||
<div id="securityOverrideDiv">
|
||||
<a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
|
||||
<div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Retry Button -->
|
||||
<button id="errorTryAgain" onclick="retryThis(this);">&retry.label;</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
var PreferencesPanelView = {
|
||||
init: function pv_init() {
|
||||
// Run some setup code the first time the panel is shown.
|
||||
Elements.prefsFlyout.addEventListener("PopupChanged", function onShow(aEvent) {
|
||||
if (aEvent.detail && aEvent.popup === Elements.prefsFlyout) {
|
||||
Elements.prefsFlyout.removeEventListener("PopupChanged", onShow, false);
|
||||
MasterPasswordUI.updatePreference();
|
||||
WeaveGlue.init();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
<dialog id="capturepicker-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="document.getElementById('capturepicker-dialog').CaptureDialog.init()"
|
||||
script="chrome://browser/content/CaptureDialog.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('capturepicker-dialog').CaptureDialog.capture();"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('capturepicker-dialog').CaptureDialog.cancel();"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="capturepicker-title" class="prompt-title" crop="center" flex="1"/>
|
||||
<hbox flex="1" pack="center">
|
||||
<vbox id="capturepicker-container" pack="center">
|
||||
<video xmlns="http://www.w3.org/1999/xhtml" id="capturepicker-video"
|
||||
width="320" height="240"
|
||||
src="moz-device:?width=320&height=240&type=video/x-raw-yuv"
|
||||
autoplay="true"> </video>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<hbox id="capturepicker-buttons-box" class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE prompt SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
|
||||
<dialog id="prompt-alert-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="document.getElementById('prompt-button-ok').focus()"
|
||||
onclose="this.PromptHelper.onCloseAlert(this);"
|
||||
script="chrome://browser/content/prompt/prompt.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('prompt-alert-dialog').close()"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('prompt-alert-dialog').close()"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="prompt-alert-title" class="prompt-title" crop="center" flex="1"/>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<description id="prompt-alert-message"/>
|
||||
</scrollbox>
|
||||
|
||||
<checkbox id="prompt-alert-checkbox" collapsed="true" pack="start" flex="1" />
|
||||
</vbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button id="prompt-button-ok" class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
|
||||
<dialog id="prompt-confirm-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="document.getElementsByAttribute('command', 'cmd_ok')[0].focus()"
|
||||
onclose="this.PromptHelper.onCloseConfirm(this)"
|
||||
script="chrome://browser/content/prompt/prompt.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('prompt-confirm-dialog').PromptHelper.closeConfirm(true)"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('prompt-confirm-dialog').PromptHelper.closeConfirm(false)"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="prompt-confirm-title" class="prompt-title" crop="center" flex="1"/>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<description id="prompt-confirm-message"/>
|
||||
</scrollbox>
|
||||
|
||||
<checkbox id="prompt-confirm-checkbox" collapsed="true" pack="start" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<hbox id="prompt-confirm-buttons-box" class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE dialog [
|
||||
<!ENTITY % dialog SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
<!ENTITY % changempDTD SYSTEM "chrome://mozapps/locale/preferences/changemp.dtd" >
|
||||
%dialog;
|
||||
%changempDTD;
|
||||
]>
|
||||
|
||||
<dialog id="masterpassword-change" title="&setPassword.title;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="MasterPasswordUI.setPassword();"/>
|
||||
<command id="cmd_cancel" oncommand="MasterPasswordUI.hide();"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="masterpassword-title" class="prompt-title" crop="center" flex="1">&setPassword.title;</description>
|
||||
</vbox>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<label control="masterpassword-newpassword1" value="&setPassword.newPassword.label;"/>
|
||||
<textbox id="masterpassword-newpassword1" type="password" oninput="MasterPasswordUI.checkPassword();" flex="1"/>
|
||||
<label control="masterpassword-newpassword2" value="&setPassword.reenterPassword.label;"/>
|
||||
<textbox id="masterpassword-newpassword2" type="password" oninput="MasterPasswordUI.checkPassword();" flex="1"/>
|
||||
<separator/>
|
||||
</scrollbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,72 @@
|
|||
/* 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/. */
|
||||
|
||||
const PromptHelper = {
|
||||
closeDialog: function(confirm, id) {
|
||||
let dialog = document.getElementById(id);
|
||||
if (typeof confirm == "boolean" && dialog.arguments && "defaultButton" in dialog.arguments)
|
||||
// confirmEx always returns 1 when dismissed with "escape" (bug 345067).
|
||||
dialog.arguments.result = confirm ? dialog.arguments.defaultButton : 1;
|
||||
else
|
||||
dialog.arguments.result = confirm;
|
||||
dialog.close();
|
||||
},
|
||||
|
||||
// Alert dialog
|
||||
onCloseAlert: function(dialog) {
|
||||
if (dialog.arguments)
|
||||
dialog.arguments.value = document.getElementById("prompt-alert-checkbox").checked;
|
||||
},
|
||||
|
||||
// Confirm dialog
|
||||
closeConfirm: function(confirm) {
|
||||
this.closeDialog(confirm, "prompt-confirm-dialog");
|
||||
},
|
||||
|
||||
onCloseConfirm: function(dialog) {
|
||||
if (dialog.arguments && ("checkbox" in dialog.arguments))
|
||||
dialog.arguments.checkbox.value = document.getElementById("prompt-confirm-checkbox").checked;
|
||||
},
|
||||
|
||||
// Prompt dialog
|
||||
closePrompt: function(confirm) {
|
||||
this.closeDialog(confirm, "prompt-prompt-dialog");
|
||||
},
|
||||
|
||||
onClosePrompt: function(dialog) {
|
||||
if (dialog.arguments) {
|
||||
dialog.arguments.checkbox.value = document.getElementById("prompt-prompt-checkbox").checked;
|
||||
dialog.arguments.value.value = document.getElementById("prompt-prompt-textbox").value;
|
||||
}
|
||||
},
|
||||
|
||||
// User / Password dialog
|
||||
onLoadPassword: function onLoadPassword(dialog) {
|
||||
let user = document.getElementById('prompt-password-user');
|
||||
if (!user.value)
|
||||
user.focus();
|
||||
},
|
||||
|
||||
closePassword: function(confirm) {
|
||||
this.closeDialog(confirm, "prompt-password-dialog");
|
||||
},
|
||||
|
||||
onClosePassword: function(dialog) {
|
||||
if (dialog.arguments) {
|
||||
dialog.arguments.checkbox.value = document.getElementById("prompt-password-checkbox").checked;
|
||||
dialog.arguments.user.value = document.getElementById("prompt-password-user").value;
|
||||
dialog.arguments.password.value = document.getElementById("prompt-password-password").value;
|
||||
}
|
||||
},
|
||||
|
||||
// Select dialog
|
||||
closeSelect: function(confirm) {
|
||||
this.closeDialog(confirm, "prompt-select-dialog");
|
||||
},
|
||||
|
||||
onCloseSelect: function(dialog) {
|
||||
if (dialog.arguments)
|
||||
dialog.arguments.selection.value = document.getElementById("prompt-select-list").selectedIndex;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE prompt SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
|
||||
<dialog id="prompt-prompt-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="document.getElementById('prompt-prompt-textbox').focus()"
|
||||
onclose="this.PromptHelper.onClosePrompt(this)"
|
||||
script="chrome://browser/content/prompt/prompt.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('prompt-prompt-dialog').PromptHelper.closePrompt(true)"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('prompt-prompt-dialog').PromptHelper.closePrompt(false)"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="prompt-prompt-title" class="prompt-title" crop="center" flex="1"/>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<description id="prompt-prompt-message"/>
|
||||
</scrollbox>
|
||||
|
||||
<textbox id="prompt-prompt-textbox" class="prompt-edit"/>
|
||||
|
||||
<button id="prompt-prompt-checkbox" collapsed="true" pack="start" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE prompt [
|
||||
<!ENTITY % promptDTD SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
<!ENTITY % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd">
|
||||
%promptDTD;
|
||||
%commonDialogDTD;
|
||||
]>
|
||||
|
||||
<dialog id="prompt-password-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="this.PromptHelper.onLoadPassword(this)"
|
||||
onclose="this.PromptHelper.onClosePassword(this)"
|
||||
script="chrome://browser/content/prompt/prompt.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('prompt-password-dialog').PromptHelper.closePassword(true)"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('prompt-password-dialog').PromptHelper.closePassword(false)"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="prompt-password-title" class="prompt-title" crop="center" flex="1"/>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<description id="prompt-password-message"/>
|
||||
</scrollbox>
|
||||
|
||||
<grid class="prompt-message">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row align="center">
|
||||
<label value="&editfield0.label;"/>
|
||||
<textbox id="prompt-password-user" class="prompt-edit"/>
|
||||
</row>
|
||||
<row align="center">
|
||||
<label value="&editfield1.label;"/>
|
||||
<textbox type="password" id="prompt-password-password" class="prompt-edit"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
<checkbox id="prompt-password-checkbox" collapsed="true" pack="start" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE dialog [
|
||||
<!ENTITY % dialog SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
<!ENTITY % removempDTD SYSTEM "chrome://mozapps/locale/preferences/removemp.dtd" >
|
||||
%dialog;
|
||||
%removempDTD;
|
||||
]>
|
||||
|
||||
<dialog id="masterpassword-remove" title="&removePassword.title;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="MasterPasswordUI.removePassword();"/>
|
||||
<command id="cmd_cancel" oncommand="MasterPasswordUI.hide();"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="masterpassword-title" class="prompt-title" crop="center" flex="1">&removePassword.title;</description>
|
||||
</vbox>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<label control="masterpassword-oldpassword" value="&setPassword.oldPassword.label;"/>
|
||||
<textbox id="masterpassword-oldpassword" type="password" oninput="MasterPasswordUI.checkOldPassword();" flex="1"/>
|
||||
<separator/>
|
||||
</scrollbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" disabled="true" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE prompt SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
|
||||
<dialog id="prompt-select-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="document.getElementById('prompt-select-list').focus()"
|
||||
onclose="this.PromptHelper.onCloseSelect(this)"
|
||||
script="chrome://browser/content/prompt/prompt.js">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_RETURN" command="cmd_ok"/>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_ok" oncommand="document.getElementById('prompt-select-dialog').PromptHelper.closeSelect(true)"/>
|
||||
<command id="cmd_cancel" oncommand="document.getElementById('prompt-select-dialog').PromptHelper.closeSelect(false)"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<vbox class="prompt-header" flex="1">
|
||||
<description id="prompt-select-title" class="prompt-title" crop="center" flex="1"/>
|
||||
|
||||
<scrollbox orient="vertical" class="prompt-message" flex="1">
|
||||
<description id="prompt-select-message"/>
|
||||
</scrollbox>
|
||||
|
||||
<menulist id="prompt-select-list"/>
|
||||
</vbox>
|
||||
|
||||
<hbox class="prompt-buttons">
|
||||
<button class="prompt-button" label="&ok.label;" command="cmd_ok"/>
|
||||
<button class="prompt-button" label="&cancel.label;" command="cmd_cancel"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE prompt SYSTEM "chrome://browser/locale/prompt.dtd">
|
||||
|
||||
<dialog id="share-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
|
||||
</keyset>
|
||||
|
||||
<commandset>
|
||||
<command id="cmd_cancel" oncommand="SharingUI.hide();"/>
|
||||
</commandset>
|
||||
|
||||
<vbox class="prompt-inner">
|
||||
<hbox class="prompt-header">
|
||||
<label id="share-title" crop="center" flex="1"/>
|
||||
</hbox>
|
||||
|
||||
<hbox id="share-buttons-box" class="prompt-buttons"/>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -0,0 +1,315 @@
|
|||
// -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
/* 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/. */
|
||||
|
||||
function Sanitizer() {}
|
||||
Sanitizer.prototype = {
|
||||
// warning to the caller: this one may raise an exception (e.g. bug #265028)
|
||||
clearItem: function (aItemName)
|
||||
{
|
||||
if (this.items[aItemName].canClear)
|
||||
this.items[aItemName].clear();
|
||||
},
|
||||
|
||||
canClearItem: function (aItemName)
|
||||
{
|
||||
return this.items[aItemName].canClear;
|
||||
},
|
||||
|
||||
_prefDomain: "privacy.item.",
|
||||
getNameFromPreference: function (aPreferenceName)
|
||||
{
|
||||
return aPreferenceName.substr(this._prefDomain.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes privacy sensitive data in a batch, according to user preferences
|
||||
*
|
||||
* @returns null if everything's fine; an object in the form
|
||||
* { itemName: error, ... } on (partial) failure
|
||||
*/
|
||||
sanitize: function ()
|
||||
{
|
||||
var branch = Services.prefs.getBranch(this._prefDomain);
|
||||
var errors = null;
|
||||
for (var itemName in this.items) {
|
||||
var item = this.items[itemName];
|
||||
if ("clear" in item && item.canClear && branch.getBoolPref(itemName)) {
|
||||
// Some of these clear() may raise exceptions (see bug #265028)
|
||||
// to sanitize as much as possible, we catch and store them,
|
||||
// rather than fail fast.
|
||||
// Callers should check returned errors and give user feedback
|
||||
// about items that could not be sanitized
|
||||
try {
|
||||
item.clear();
|
||||
} catch(er) {
|
||||
if (!errors)
|
||||
errors = {};
|
||||
errors[itemName] = er;
|
||||
dump("Error sanitizing " + itemName + ": " + er + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
|
||||
items: {
|
||||
// Clear Sync account before passwords so that Sync still has access to the
|
||||
// credentials to clean up device-specific records on the server. Also
|
||||
// disable it before wiping history so we don't accidentally sync that.
|
||||
syncAccount: {
|
||||
clear: function ()
|
||||
{
|
||||
WeaveGlue.disconnect();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
|
||||
}
|
||||
},
|
||||
|
||||
cache: {
|
||||
clear: function ()
|
||||
{
|
||||
var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
|
||||
try {
|
||||
cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
|
||||
} catch(er) {}
|
||||
|
||||
let imageCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
|
||||
try {
|
||||
imageCache.clearCache(false); // true=chrome, false=content
|
||||
} catch(er) {}
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
cookies: {
|
||||
clear: function ()
|
||||
{
|
||||
var cookieMgr = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
|
||||
cookieMgr.removeAll();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
geolocation: {
|
||||
clear: function ()
|
||||
{
|
||||
// clear any network geolocation provider sessions
|
||||
try {
|
||||
var branch = Services.prefs.getBranch("geo.wifi.access_token.");
|
||||
branch.deleteBranch("");
|
||||
|
||||
branch = Services.prefs.getBranch("geo.request.remember.");
|
||||
branch.deleteBranch("");
|
||||
} catch (e) {dump(e);}
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
siteSettings: {
|
||||
clear: function ()
|
||||
{
|
||||
// Clear site-specific permissions like "Allow this site to open popups"
|
||||
Services.perms.removeAll();
|
||||
|
||||
// Clear site-specific settings like page-zoom level
|
||||
var cps = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService);
|
||||
cps.removeGroupedPrefs();
|
||||
|
||||
// Clear "Never remember passwords for this site", which is not handled by
|
||||
// the permission manager
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
||||
var hosts = pwmgr.getAllDisabledHosts({})
|
||||
for each (var host in hosts) {
|
||||
pwmgr.setLoginSavingEnabled(host, true);
|
||||
}
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
offlineApps: {
|
||||
clear: function ()
|
||||
{
|
||||
var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
|
||||
try {
|
||||
cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
|
||||
} catch(er) {}
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
history: {
|
||||
clear: function ()
|
||||
{
|
||||
var globalHistory = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIBrowserHistory);
|
||||
globalHistory.removeAllPages();
|
||||
|
||||
try {
|
||||
Services.obs.notifyObservers(null, "browser:purge-session-history", "");
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
// Clear last URL of the Open Web Location dialog
|
||||
try {
|
||||
Services.prefs.clearUserPref("general.open_location.last_url");
|
||||
}
|
||||
catch (e) { }
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
// bug 347231: Always allow clearing history due to dependencies on
|
||||
// the browser:purge-session-history notification. (like error console)
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
formdata: {
|
||||
clear: function ()
|
||||
{
|
||||
//Clear undo history of all searchBars
|
||||
var windows = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windows.hasMoreElements()) {
|
||||
var searchBar = windows.getNext().document.getElementById("searchbar");
|
||||
if (searchBar) {
|
||||
searchBar.value = "";
|
||||
searchBar.textbox.editor.transactionManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
var formHistory = Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
|
||||
formHistory.removeAllEntries();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
var formHistory = Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
|
||||
return formHistory.hasEntries;
|
||||
}
|
||||
},
|
||||
|
||||
downloads: {
|
||||
clear: function ()
|
||||
{
|
||||
var dlMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
dlMgr.cleanUp();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
var dlMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
return dlMgr.canCleanUp;
|
||||
}
|
||||
},
|
||||
|
||||
passwords: {
|
||||
clear: function ()
|
||||
{
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
||||
pwmgr.removeAllLogins();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
||||
var count = pwmgr.countLogins("", "", ""); // count all logins
|
||||
return (count > 0);
|
||||
}
|
||||
},
|
||||
|
||||
sessions: {
|
||||
clear: function ()
|
||||
{
|
||||
// clear all auth tokens
|
||||
var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
|
||||
sdr.logoutAndTeardown();
|
||||
|
||||
// clear plain HTTP auth sessions
|
||||
var authMgr = Cc['@mozilla.org/network/http-auth-manager;1'].getService(Ci.nsIHttpAuthManager);
|
||||
authMgr.clearAll();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// "Static" members
|
||||
Sanitizer.prefDomain = "privacy.sanitize.";
|
||||
Sanitizer.prefShutdown = "sanitizeOnShutdown";
|
||||
Sanitizer.prefDidShutdown = "didShutdownSanitize";
|
||||
|
||||
Sanitizer._prefs = null;
|
||||
Sanitizer.__defineGetter__("prefs", function()
|
||||
{
|
||||
return Sanitizer._prefs ? Sanitizer._prefs
|
||||
: Sanitizer._prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService)
|
||||
.getBranch(Sanitizer.prefDomain);
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes privacy sensitive data in a batch, optionally showing the
|
||||
* sanitize UI, according to user preferences
|
||||
*
|
||||
* @returns null if everything's fine
|
||||
* an object in the form { itemName: error, ... } on (partial) failure
|
||||
*/
|
||||
Sanitizer.sanitize = function()
|
||||
{
|
||||
return new Sanitizer().sanitize();
|
||||
};
|
||||
|
||||
Sanitizer.onStartup = function()
|
||||
{
|
||||
// we check for unclean exit with pending sanitization
|
||||
Sanitizer._checkAndSanitize();
|
||||
};
|
||||
|
||||
Sanitizer.onShutdown = function()
|
||||
{
|
||||
// we check if sanitization is needed and perform it
|
||||
Sanitizer._checkAndSanitize();
|
||||
};
|
||||
|
||||
// this is called on startup and shutdown, to perform pending sanitizations
|
||||
Sanitizer._checkAndSanitize = function()
|
||||
{
|
||||
const prefs = Sanitizer.prefs;
|
||||
if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
|
||||
!prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
|
||||
// this is a shutdown or a startup after an unclean exit
|
||||
Sanitizer.sanitize() || // sanitize() returns null on full success
|
||||
prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,678 @@
|
|||
/* 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/. */
|
||||
|
||||
let WeaveGlue = {
|
||||
setupData: null,
|
||||
_boundOnEngineSync: null, // Needed to unhook the observers in close().
|
||||
_boundOnServiceSync: null,
|
||||
jpake: null,
|
||||
_bundle: null,
|
||||
_loginError: false,
|
||||
_progressBar: null,
|
||||
_progressValue: 0,
|
||||
_progressMax: null,
|
||||
|
||||
init: function init() {
|
||||
if (this._bundle) {
|
||||
return;
|
||||
}
|
||||
|
||||
let service = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
if (service.ready) {
|
||||
this._init();
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.addObserver(function onReady() {
|
||||
Services.obs.removeObserver(onReady, "weave:service:ready");
|
||||
this._init();
|
||||
}.bind(this), "weave:service:ready", false);
|
||||
|
||||
service.ensureLoaded();
|
||||
},
|
||||
|
||||
_init: function () {
|
||||
this._bundle = Services.strings.createBundle("chrome://browser/locale/sync.properties");
|
||||
this._msg = document.getElementById("prefs-messages");
|
||||
|
||||
this._addListeners();
|
||||
|
||||
this.setupData = { account: "", password: "" , synckey: "", serverURL: "" };
|
||||
|
||||
if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
|
||||
// Put the settings UI into a state of "connecting..." if we are going to auto-connect
|
||||
this._elements.connect.firstChild.disabled = true;
|
||||
this._elements.connect.setAttribute("title", this._bundle.GetStringFromName("connecting.label"));
|
||||
|
||||
try {
|
||||
this._elements.device.value = Services.prefs.getCharPref("services.sync.client.name");
|
||||
} catch(e) {}
|
||||
} else if (Weave.Status.login != Weave.LOGIN_FAILED_NO_USERNAME) {
|
||||
this.loadSetupData();
|
||||
}
|
||||
this._boundOnEngineSync = this.onEngineSync.bind(this);
|
||||
this._boundOnServiceSync = this.onServiceSync.bind(this);
|
||||
this._progressBar = document.getElementById("syncsetup-progressbar");
|
||||
},
|
||||
|
||||
abortEasySetup: function abortEasySetup() {
|
||||
document.getElementById("syncsetup-code1").value = "....";
|
||||
document.getElementById("syncsetup-code2").value = "....";
|
||||
document.getElementById("syncsetup-code3").value = "....";
|
||||
if (!this.jpake)
|
||||
return;
|
||||
|
||||
this.jpake.abort();
|
||||
this.jpake = null;
|
||||
},
|
||||
|
||||
_resetScrollPosition: function _resetScrollPosition() {
|
||||
let scrollboxes = document.getElementsByClassName("syncsetup-scrollbox");
|
||||
for (let i = 0; i < scrollboxes.length; i++) {
|
||||
let sbo = scrollboxes[i].boxObject.QueryInterface(Ci.nsIScrollBoxObject);
|
||||
try {
|
||||
sbo.scrollTo(0, 0);
|
||||
} catch(e) {}
|
||||
}
|
||||
},
|
||||
|
||||
open: function open() {
|
||||
let container = document.getElementById("syncsetup-container");
|
||||
if (!container.hidden)
|
||||
return;
|
||||
|
||||
// Services.io.offline is lying to us, so we use the NetworkLinkService instead
|
||||
let nls = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
|
||||
if (!nls.isLinkUp) {
|
||||
Services.obs.notifyObservers(null, "browser:sync:setup:networkerror", "");
|
||||
Services.prompt.alert(window,
|
||||
this._bundle.GetStringFromName("sync.setup.error.title"),
|
||||
this._bundle.GetStringFromName("sync.setup.error.network"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear up any previous JPAKE codes
|
||||
this.abortEasySetup();
|
||||
|
||||
// Show the connect UI
|
||||
container.hidden = false;
|
||||
document.getElementById("syncsetup-simple").hidden = false;
|
||||
document.getElementById("syncsetup-waiting").hidden = true;
|
||||
document.getElementById("syncsetup-fallback").hidden = true;
|
||||
|
||||
DialogUI.pushDialog(this);
|
||||
|
||||
let self = this;
|
||||
this.jpake = new Weave.JPAKEClient({
|
||||
displayPIN: function displayPIN(aPin) {
|
||||
document.getElementById("syncsetup-code1").value = aPin.slice(0, 4);
|
||||
document.getElementById("syncsetup-code2").value = aPin.slice(4, 8);
|
||||
document.getElementById("syncsetup-code3").value = aPin.slice(8);
|
||||
},
|
||||
|
||||
onPairingStart: function onPairingStart() {
|
||||
document.getElementById("syncsetup-simple").hidden = true;
|
||||
document.getElementById("syncsetup-waiting").hidden = false;
|
||||
},
|
||||
|
||||
onComplete: function onComplete(aCredentials) {
|
||||
self.jpake = null;
|
||||
|
||||
self._progressBar.mode = "determined";
|
||||
document.getElementById("syncsetup-waiting-desc").hidden = true;
|
||||
document.getElementById("syncsetup-waiting-cancel").hidden = true;
|
||||
document.getElementById("syncsetup-waitingdownload-desc").hidden = false;
|
||||
document.getElementById("syncsetup-waiting-close").hidden = false;
|
||||
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:finish", false);
|
||||
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:error", false);
|
||||
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:finish", false);
|
||||
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:error", false);
|
||||
self.setupData = aCredentials;
|
||||
self.connect();
|
||||
},
|
||||
|
||||
onAbort: function onAbort(aError) {
|
||||
self.jpake = null;
|
||||
|
||||
if (aError == "jpake.error.userabort" || container.hidden) {
|
||||
Services.obs.notifyObservers(null, "browser:sync:setup:userabort", "");
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatically go to manual setup if we couldn't acquire a channel.
|
||||
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
|
||||
let tryAgain = self._bundle.GetStringFromName("sync.setup.tryagain");
|
||||
let manualSetup = self._bundle.GetStringFromName("sync.setup.manual");
|
||||
let buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_2);
|
||||
|
||||
let button = Services.prompt.confirmEx(window,
|
||||
self._bundle.GetStringFromName("sync.setup.error.title"),
|
||||
self._bundle.formatStringFromName("sync.setup.error.nodata", [brandShortName], 1),
|
||||
buttonFlags, tryAgain, manualSetup, null, "", {});
|
||||
switch (button) {
|
||||
case 0:
|
||||
// we have to build a new JPAKEClient here rather than reuse the old one
|
||||
container.hidden = true;
|
||||
self.open();
|
||||
break;
|
||||
case 1:
|
||||
self.openManual();
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
self.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.jpake.receiveNoPIN();
|
||||
},
|
||||
|
||||
openManual: function openManual() {
|
||||
this.abortEasySetup();
|
||||
|
||||
// Reset the scroll since the previous page might have been scrolled
|
||||
this._resetScrollPosition();
|
||||
|
||||
document.getElementById("syncsetup-simple").hidden = true;
|
||||
document.getElementById("syncsetup-waiting").hidden = true;
|
||||
document.getElementById("syncsetup-fallback").hidden = false;
|
||||
|
||||
// Push the current setup data into the UI
|
||||
if (this.setupData && "account" in this.setupData) {
|
||||
this._elements.account.value = this.setupData.account;
|
||||
this._elements.password.value = this.setupData.password;
|
||||
let pp = this.setupData.synckey;
|
||||
if (Weave.Utils.isPassphrase(pp))
|
||||
pp = Weave.Utils.hyphenatePassphrase(pp);
|
||||
this._elements.synckey.value = pp;
|
||||
if (this.setupData.serverURL && this.setupData.serverURL.length) {
|
||||
this._elements.usecustomserver.checked = true;
|
||||
this._elements.customserver.disabled = false;
|
||||
this._elements.customserver.value = this.setupData.serverURL;
|
||||
} else {
|
||||
this._elements.usecustomserver.checked = false;
|
||||
this._elements.customserver.disabled = true;
|
||||
this._elements.customserver.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
this.canConnect();
|
||||
},
|
||||
|
||||
onEngineSync: function onEngineSync(subject, topic, data) {
|
||||
// The Clients engine syncs first. At this point we don't necessarily know
|
||||
// yet how many engines will be enabled, so we'll ignore the Clients engine
|
||||
// and evaluate how many engines are enabled when the first "real" engine
|
||||
// syncs.
|
||||
if (data == 'clients') {
|
||||
return;
|
||||
}
|
||||
if (this._progressMax == null) {
|
||||
this._progressMax = Weave.Service.engineManager.getEnabled().length;
|
||||
this._progressBar.max = this._progressMax;
|
||||
}
|
||||
this._progressValue += 1;
|
||||
this._progressBar.setAttribute("value", this._progressValue);
|
||||
},
|
||||
|
||||
onServiceSync: function onServiceSync() {
|
||||
this.close();
|
||||
},
|
||||
|
||||
close: function close() {
|
||||
try {
|
||||
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:finish");
|
||||
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:error");
|
||||
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:finish");
|
||||
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:error");
|
||||
}
|
||||
catch(e) {
|
||||
// Observers weren't registered because we never got as far as onComplete.
|
||||
}
|
||||
|
||||
if (this.jpake)
|
||||
this.abortEasySetup();
|
||||
|
||||
// Reset the scroll since the previous page might have been scrolled
|
||||
this._resetScrollPosition();
|
||||
|
||||
// Save current setup data
|
||||
this.setupData = {
|
||||
account: this._elements.account.value.trim(),
|
||||
password: this._elements.password.value.trim(),
|
||||
synckey: Weave.Utils.normalizePassphrase(this._elements.synckey.value.trim()),
|
||||
serverURL: this._validateServer(this._elements.customserver.value.trim())
|
||||
};
|
||||
|
||||
// Clear the UI so it's ready for next time
|
||||
this._elements.account.value = "";
|
||||
this._elements.password.value = "";
|
||||
this._elements.synckey.value = "";
|
||||
this._elements.usecustomserver.checked = false;
|
||||
this._elements.customserver.disabled = true;
|
||||
this._elements.customserver.value = "";
|
||||
document.getElementById("syncsetup-waiting-desc").hidden = false;
|
||||
document.getElementById("syncsetup-waiting-cancel").hidden = false;
|
||||
document.getElementById("syncsetup-waitingdownload-desc").hidden = true;
|
||||
document.getElementById("syncsetup-waiting-close").hidden = true;
|
||||
this._progressMax = null;
|
||||
this._progressValue = 0;
|
||||
this._progressBar.max = 0;
|
||||
this._progressBar.value = 0;
|
||||
this._progressBar.mode = "undetermined";
|
||||
|
||||
// Close the connect UI
|
||||
document.getElementById("syncsetup-container").hidden = true;
|
||||
DialogUI.popDialog();
|
||||
},
|
||||
|
||||
toggleCustomServer: function toggleCustomServer() {
|
||||
let useCustomServer = this._elements.usecustomserver.checked;
|
||||
this._elements.customserver.disabled = !useCustomServer;
|
||||
if (!useCustomServer)
|
||||
this._elements.customserver.value = "";
|
||||
},
|
||||
|
||||
canConnect: function canConnect() {
|
||||
let account = this._elements.account.value;
|
||||
let password = this._elements.password.value;
|
||||
let synckey = this._elements.synckey.value;
|
||||
|
||||
let disabled = !(account && password && synckey);
|
||||
document.getElementById("syncsetup-button-connect").disabled = disabled;
|
||||
},
|
||||
|
||||
showDetails: function showDetails() {
|
||||
// Show the connect UI detail settings
|
||||
let show = this._elements.sync.collapsed;
|
||||
this._elements.details.checked = show;
|
||||
this._elements.sync.collapsed = !show;
|
||||
this._elements.device.collapsed = !show;
|
||||
this._elements.disconnect.collapsed = !show;
|
||||
},
|
||||
|
||||
tryConnect: function login() {
|
||||
// If Sync is not configured, simply show the setup dialog
|
||||
if (this._loginError || Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
|
||||
this.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// No setup data, do nothing
|
||||
if (!this.setupData)
|
||||
return;
|
||||
|
||||
if (this.setupData.serverURL && this.setupData.serverURL.length)
|
||||
Weave.Service.serverURL = this.setupData.serverURL;
|
||||
|
||||
// We might still be in the middle of a sync from before Sync was disabled, so
|
||||
// let's force the UI into a state that the Sync code feels comfortable
|
||||
this.observe(null, "", "");
|
||||
|
||||
// Now try to re-connect. If successful, this will reset the UI into the
|
||||
// correct state automatically.
|
||||
Weave.Service.login(Weave.Service.identity.username, this.setupData.password, this.setupData.synckey);
|
||||
},
|
||||
|
||||
connect: function connect(aSetupData) {
|
||||
// Use setup data to pre-configure manual fields
|
||||
if (aSetupData)
|
||||
this.setupData = aSetupData;
|
||||
|
||||
// Cause the Sync system to reset internals if we seem to be switching accounts
|
||||
if (this.setupData.account != Weave.Service.identity.account)
|
||||
Weave.Service.startOver();
|
||||
|
||||
// Remove any leftover connection error string
|
||||
this._elements.connect.removeAttribute("desc");
|
||||
|
||||
// Reset the custom server URL, if we have one
|
||||
if (this.setupData.serverURL && this.setupData.serverURL.length)
|
||||
Weave.Service.serverURL = this.setupData.serverURL;
|
||||
|
||||
// Sync will use the account value and munge it into a username, as needed
|
||||
Weave.Service.identity.account = this.setupData.account;
|
||||
Weave.Service.identity.basicPassword = this.setupData.password;
|
||||
Weave.Service.identity.syncKey = this.setupData.synckey;
|
||||
Weave.Service.persistLogin();
|
||||
Weave.Svc.Obs.notify("weave:service:setup-complete");
|
||||
setTimeout(function () { Weave.Service.sync(); }, 0);
|
||||
},
|
||||
|
||||
disconnect: function disconnect() {
|
||||
// Save credentials for undo
|
||||
let undoData = this.setupData;
|
||||
|
||||
// Remove all credentials
|
||||
this.setupData = null;
|
||||
Weave.Service.startOver();
|
||||
|
||||
let message = this._bundle.GetStringFromName("notificationDisconnect.label");
|
||||
let button = this._bundle.GetStringFromName("notificationDisconnect.button");
|
||||
let buttons = [ {
|
||||
label: button,
|
||||
accessKey: "",
|
||||
callback: function() { WeaveGlue.connect(undoData); }
|
||||
} ];
|
||||
this.showMessage(message, "undo-disconnect", buttons);
|
||||
|
||||
// Hide the notification when the panel is changed or closed.
|
||||
let panel = document.getElementById("prefs-container");
|
||||
panel.addEventListener("ToolPanelHidden", function onHide(aEvent) {
|
||||
panel.removeEventListener(aEvent.type, onHide, false);
|
||||
let notification = WeaveGlue._msg.getNotificationWithValue("undo-disconnect");
|
||||
if (notification)
|
||||
notification.close();
|
||||
}, false);
|
||||
|
||||
Weave.Service.logout();
|
||||
},
|
||||
|
||||
sync: function sync() {
|
||||
Weave.Service.sync();
|
||||
},
|
||||
|
||||
_addListeners: function _addListeners() {
|
||||
let topics = ["weave:service:setup-complete",
|
||||
"weave:service:sync:start", "weave:service:sync:finish",
|
||||
"weave:service:sync:error", "weave:service:login:start",
|
||||
"weave:service:login:finish", "weave:service:login:error",
|
||||
"weave:ui:login:error",
|
||||
"weave:service:logout:finish"];
|
||||
|
||||
// For each topic, add WeaveGlue the observer
|
||||
topics.forEach(function(topic) {
|
||||
Services.obs.addObserver(WeaveGlue, topic, false);
|
||||
});
|
||||
|
||||
// Remove them on unload
|
||||
addEventListener("unload", function() {
|
||||
topics.forEach(function(topic) {
|
||||
Services.obs.removeObserver(WeaveGlue, topic, false);
|
||||
});
|
||||
}, false);
|
||||
},
|
||||
|
||||
get _elements() {
|
||||
// Do a quick test to see if the options exist yet
|
||||
let syncButton = document.getElementById("sync-syncButton");
|
||||
if (syncButton == null)
|
||||
return null;
|
||||
|
||||
// Get all the setting nodes from the add-ons display
|
||||
let elements = {};
|
||||
let setupids = ["account", "password", "synckey", "usecustomserver", "customserver"];
|
||||
setupids.forEach(function(id) {
|
||||
elements[id] = document.getElementById("syncsetup-" + id);
|
||||
});
|
||||
|
||||
let settingids = ["device", "connect", "connected", "disconnect", "sync", "details", "pairdevice"];
|
||||
settingids.forEach(function(id) {
|
||||
elements[id] = document.getElementById("sync-" + id);
|
||||
});
|
||||
|
||||
// Replace the getter with the collection of settings
|
||||
delete this._elements;
|
||||
return this._elements = elements;
|
||||
},
|
||||
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
// Make sure we're online when connecting/syncing
|
||||
Util.forceOnline();
|
||||
|
||||
// Can't do anything before settings are loaded
|
||||
if (this._elements == null)
|
||||
return;
|
||||
|
||||
// Make some aliases
|
||||
let connect = this._elements.connect;
|
||||
let connected = this._elements.connected;
|
||||
let details = this._elements.details;
|
||||
let device = this._elements.device;
|
||||
let disconnect = this._elements.disconnect;
|
||||
let sync = this._elements.sync;
|
||||
let pairdevice = this._elements.pairdevice;
|
||||
|
||||
// Show what went wrong with login if necessary
|
||||
if (aTopic == "weave:ui:login:error") {
|
||||
this._loginError = true;
|
||||
connect.setAttribute("desc", Weave.Utils.getErrorString(Weave.Status.login));
|
||||
} else {
|
||||
connect.removeAttribute("desc");
|
||||
}
|
||||
|
||||
if (aTopic == "weave:service:login:finish") {
|
||||
this._loginError = false;
|
||||
// Init the setup data if we just logged in
|
||||
if (!this.setupData)
|
||||
this.loadSetupData();
|
||||
}
|
||||
|
||||
let isConfigured = (!this._loginError && Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
|
||||
|
||||
connect.collapsed = isConfigured;
|
||||
connected.collapsed = !isConfigured;
|
||||
|
||||
if (!isConfigured) {
|
||||
connect.setAttribute("title", this._bundle.GetStringFromName("notconnected.label"));
|
||||
connect.firstChild.disabled = false;
|
||||
details.checked = false;
|
||||
sync.collapsed = true;
|
||||
device.collapsed = true;
|
||||
disconnect.collapsed = true;
|
||||
}
|
||||
|
||||
// Check the lock on a timeout because it's set just after notifying
|
||||
setTimeout(function(self) {
|
||||
// Prevent certain actions when the service is locked
|
||||
if (Weave.Service.locked) {
|
||||
connect.firstChild.disabled = true;
|
||||
sync.firstChild.disabled = true;
|
||||
|
||||
if (aTopic == "weave:service:login:start")
|
||||
connect.setAttribute("title", self._bundle.GetStringFromName("connecting.label"));
|
||||
|
||||
if (aTopic == "weave:service:sync:start")
|
||||
sync.setAttribute("title", self._bundle.GetStringFromName("lastSyncInProgress2.label"));
|
||||
} else {
|
||||
connect.firstChild.disabled = false;
|
||||
sync.firstChild.disabled = false;
|
||||
}
|
||||
}, 0, this);
|
||||
|
||||
// Dynamically generate some strings
|
||||
let accountStr = this._bundle.formatStringFromName("account.label", [Weave.Service.identity.account], 1);
|
||||
disconnect.setAttribute("title", accountStr);
|
||||
|
||||
// Show the day-of-week and time (HH:MM) of last sync
|
||||
let lastSync = Weave.Svc.Prefs.get("lastSync");
|
||||
if (lastSync != null) {
|
||||
let syncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
|
||||
let dateStr = this._bundle.formatStringFromName("lastSync2.label", [syncDate], 1);
|
||||
sync.setAttribute("title", dateStr);
|
||||
}
|
||||
|
||||
// Check for a storage format update, update the user and load the Sync update page
|
||||
if (aTopic =="weave:service:sync:error") {
|
||||
let clientOutdated = false, remoteOutdated = false;
|
||||
if (Weave.Status.sync == Weave.VERSION_OUT_OF_DATE) {
|
||||
clientOutdated = true;
|
||||
} else if (Weave.Status.sync == Weave.DESKTOP_VERSION_OUT_OF_DATE) {
|
||||
remoteOutdated = true;
|
||||
} else if (Weave.Status.service == Weave.SYNC_FAILED_PARTIAL) {
|
||||
// Some engines failed, check for per-engine compat
|
||||
for (let [engine, reason] in Iterator(Weave.Status.engines)) {
|
||||
clientOutdated = clientOutdated || reason == Weave.VERSION_OUT_OF_DATE;
|
||||
remoteOutdated = remoteOutdated || reason == Weave.DESKTOP_VERSION_OUT_OF_DATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientOutdated || remoteOutdated) {
|
||||
let brand = Services.strings.createBundle("chrome://branding/locale/brand.properties");
|
||||
let brandName = brand.GetStringFromName("brandShortName");
|
||||
|
||||
let type = clientOutdated ? "client" : "remote";
|
||||
let message = this._bundle.GetStringFromName("sync.update." + type);
|
||||
message = message.replace("#1", brandName);
|
||||
message = message.replace("#2", Services.appinfo.version);
|
||||
let title = this._bundle.GetStringFromName("sync.update.title")
|
||||
let button = this._bundle.GetStringFromName("sync.update.button")
|
||||
let close = this._bundle.GetStringFromName("sync.update.close")
|
||||
|
||||
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
|
||||
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING;
|
||||
let choice = Services.prompt.confirmEx(window, title, message, flags, button, close, null, null, {});
|
||||
if (choice == 0)
|
||||
Browser.addTab("https://services.mozilla.com/update/", true, Browser.selectedTab);
|
||||
}
|
||||
}
|
||||
|
||||
device.value = Weave.Service.clientsEngine.localName || "";
|
||||
},
|
||||
|
||||
changeName: function changeName(aInput) {
|
||||
// Make sure to update to a modified name, e.g., empty-string -> default
|
||||
Weave.Service.clientsEngine.localName = aInput.value;
|
||||
aInput.value = Weave.Service.clientsEngine.localName;
|
||||
},
|
||||
|
||||
showMessage: function showMessage(aMsg, aValue, aButtons) {
|
||||
let notification = this._msg.getNotificationWithValue(aValue);
|
||||
if (notification)
|
||||
return;
|
||||
|
||||
this._msg.appendNotification(aMsg, aValue, "", this._msg.PRIORITY_WARNING_LOW, aButtons);
|
||||
},
|
||||
|
||||
_validateServer: function _validateServer(aURL) {
|
||||
let uri = Weave.Utils.makeURI(aURL);
|
||||
|
||||
if (!uri && aURL)
|
||||
uri = Weave.Utils.makeURI("https://" + aURL);
|
||||
|
||||
if (!uri)
|
||||
return "";
|
||||
return uri.spec;
|
||||
},
|
||||
|
||||
openTutorial: function _openTutorial() {
|
||||
WeaveGlue.close();
|
||||
|
||||
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
|
||||
let url = formatter.formatURLPref("app.sync.tutorialURL");
|
||||
BrowserUI.newTab(url, Browser.selectedTab);
|
||||
},
|
||||
|
||||
loadSetupData: function _loadSetupData() {
|
||||
this.setupData = {};
|
||||
this.setupData.account = Weave.Service.identity.account || "";
|
||||
this.setupData.password = Weave.Service.identity.basicPassword || "";
|
||||
this.setupData.synckey = Weave.Service.identity.syncKey || "";
|
||||
|
||||
let serverURL = Weave.Service.serverURL;
|
||||
let defaultPrefs = Services.prefs.getDefaultBranch(null);
|
||||
if (serverURL == defaultPrefs.getCharPref("services.sync.serverURL"))
|
||||
serverURL = "";
|
||||
this.setupData.serverURL = serverURL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const PIN_PART_LENGTH = 4;
|
||||
|
||||
let SyncPairDevice = {
|
||||
jpake: null,
|
||||
|
||||
open: function open() {
|
||||
this.code1.setAttribute("maxlength", PIN_PART_LENGTH);
|
||||
this.code2.setAttribute("maxlength", PIN_PART_LENGTH);
|
||||
this.code3.setAttribute("maxlength", PIN_PART_LENGTH);
|
||||
this.nextFocusEl = {code1: this.code2,
|
||||
code2: this.code3,
|
||||
code3: this.connectbutton};
|
||||
|
||||
document.getElementById("syncpair-container").hidden = false;
|
||||
DialogUI.pushDialog(this);
|
||||
this.code1.focus();
|
||||
|
||||
// Kick off a sync. That way the server will have the most recent data from
|
||||
// this computer and it will show up immediately on the new device.
|
||||
Weave.SyncScheduler.scheduleNextSync(0);
|
||||
},
|
||||
|
||||
close: function close() {
|
||||
this.code1.value = this.code2.value = this.code3.value = "";
|
||||
this.code1.disabled = this.code2.disabled = this.code3.disabled = false;
|
||||
this.connectbutton.disabled = true;
|
||||
if (this.jpake) {
|
||||
this.jpake.abort();
|
||||
this.jpake = null;
|
||||
}
|
||||
document.getElementById("syncpair-container").hidden = true;
|
||||
DialogUI.popDialog();
|
||||
},
|
||||
|
||||
onTextBoxInput: function onTextBoxInput(textbox) {
|
||||
if (textbox && textbox.value.length == PIN_PART_LENGTH) {
|
||||
let name = textbox.id.split("-")[1];
|
||||
this.nextFocusEl[name].focus();
|
||||
}
|
||||
|
||||
this.connectbutton.disabled =
|
||||
!(this.code1.value.length == PIN_PART_LENGTH &&
|
||||
this.code2.value.length == PIN_PART_LENGTH &&
|
||||
this.code3.value.length == PIN_PART_LENGTH);
|
||||
},
|
||||
|
||||
connect: function connect() {
|
||||
let self = this;
|
||||
let jpake = this.jpake = new Weave.JPAKEClient({
|
||||
onPaired: function onPaired() {
|
||||
let credentials = {account: Weave.Service.identity.account,
|
||||
password: Weave.Service.identity.basicPassword,
|
||||
synckey: Weave.Service.identity.syncKey,
|
||||
serverURL: Weave.Service.serverURL};
|
||||
jpake.sendAndComplete(credentials);
|
||||
},
|
||||
onComplete: function onComplete() {
|
||||
self.jpake = null;
|
||||
self.close();
|
||||
|
||||
// Schedule a Sync for soonish to fetch the data uploaded by the
|
||||
// device with which we just paired.
|
||||
Weave.SyncScheduler.scheduleNextSync(Weave.SyncScheduler.activeInterval);
|
||||
},
|
||||
onAbort: function onAbort(error) {
|
||||
self.jpake = null;
|
||||
|
||||
// Aborted by user, ignore.
|
||||
if (error == Weave.JPAKE_ERROR_USERABORT) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.code1.value = self.code2.value = self.code3.value = "";
|
||||
self.code1.disabled = self.code2.disabled = self.code3.disabled = false;
|
||||
self.code1.focus();
|
||||
}
|
||||
});
|
||||
this.code1.disabled = this.code2.disabled = this.code3.disabled = true;
|
||||
this.connectbutton.disabled = true;
|
||||
|
||||
let pin = this.code1.value + this.code2.value + this.code3.value;
|
||||
let expectDelay = false;
|
||||
jpake.pairWithPIN(pin, expectDelay);
|
||||
}
|
||||
};
|
||||
["code1", "code2", "code3", "connectbutton"].forEach(function (id) {
|
||||
XPCOMUtils.defineLazyGetter(SyncPairDevice, id, function() {
|
||||
return document.getElementById("syncpair-" + id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/* 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/. */
|
||||
|
||||
var FullScreenVideo = {
|
||||
_tab: null,
|
||||
|
||||
init: function fsv_init() {
|
||||
// These come in from content.js, currently we only use Start.
|
||||
messageManager.addMessageListener("Browser:FullScreenVideo:Start", this.show.bind(this));
|
||||
messageManager.addMessageListener("Browser:FullScreenVideo:Close", this.hide.bind(this));
|
||||
messageManager.addMessageListener("Browser:FullScreenVideo:Play", this.play.bind(this));
|
||||
messageManager.addMessageListener("Browser:FullScreenVideo:Pause", this.pause.bind(this));
|
||||
|
||||
// If the screen supports brightness locks, we will utilize that, see checkBrightnessLocking()
|
||||
try {
|
||||
this.screen = null;
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
|
||||
this.screen = screenManager.primaryScreen;
|
||||
}
|
||||
catch (e) {} // The screen does not support brightness locks
|
||||
},
|
||||
|
||||
play: function() {
|
||||
this.playing = true;
|
||||
this.checkBrightnessLocking();
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
this.playing = false;
|
||||
this.checkBrightnessLocking();
|
||||
},
|
||||
|
||||
checkBrightnessLocking: function() {
|
||||
// screen manager support for metro: bug 776113
|
||||
var shouldLock = !!this.screen && !!window.fullScreen && !!this.playing;
|
||||
var locking = !!this.brightnessLocked;
|
||||
if (shouldLock == locking)
|
||||
return;
|
||||
|
||||
if (shouldLock)
|
||||
this.screen.lockMinimumBrightness(this.screen.BRIGHTNESS_FULL);
|
||||
else
|
||||
this.screen.unlockMinimumBrightness(this.screen.BRIGHTNESS_FULL);
|
||||
this.brightnessLocked = shouldLock;
|
||||
},
|
||||
|
||||
show: function fsv_show() {
|
||||
this.createTab();
|
||||
this.checkBrightnessLocking();
|
||||
},
|
||||
|
||||
hide: function fsv_hide() {
|
||||
this.checkBrightnessLocking();
|
||||
this.destroyTab();
|
||||
},
|
||||
|
||||
createTab: function fsv_createBrowser() {
|
||||
this._tab = BrowserUI.newTab("chrome://browser/content/fullscreen-video.xhtml");
|
||||
},
|
||||
|
||||
destroyTab: function fsv_destroyBrowser() {
|
||||
Browser.closeTab(this._tab);
|
||||
this._tab = null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,104 @@
|
|||
#filter substitution
|
||||
# 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/.
|
||||
|
||||
|
||||
chrome.jar:
|
||||
% content browser %content/
|
||||
|
||||
content/aboutCertError.xhtml (content/pages/aboutCertError.xhtml)
|
||||
* content/aboutRights.xhtml (content/pages/aboutRights.xhtml)
|
||||
content/aboutCrash.xhtml (content/pages/aboutCrash.xhtml)
|
||||
content/blockedSite.xhtml (content/pages/blockedSite.xhtml)
|
||||
content/fullscreen-video.xhtml (content/pages/fullscreen-video.xhtml)
|
||||
content/netError.xhtml (content/pages/netError.xhtml)
|
||||
|
||||
* content/bindings/bindings.xml (content/bindings/bindings.xml)
|
||||
content/bindings/tabs.xml (content/bindings/tabs.xml)
|
||||
content/bindings/toggleswitch.xml (content/bindings/toggleswitch.xml)
|
||||
* content/bindings/browser.xml (content/bindings/browser.xml)
|
||||
content/bindings/browser.js (content/bindings/browser.js)
|
||||
content/bindings/downloads.xml (content/bindings/downloads.xml)
|
||||
content/bindings/console.xml (content/bindings/console.xml)
|
||||
content/bindings/dialog.xml (content/bindings/dialog.xml)
|
||||
content/bindings/pageaction.xml (content/bindings/pageaction.xml)
|
||||
content/bindings/arrowbox.xml (content/bindings/arrowbox.xml)
|
||||
content/bindings/grid.xml (content/bindings/grid.xml)
|
||||
content/bindings/autocomplete.xml (content/bindings/autocomplete.xml)
|
||||
content/bindings/appbar.xml (content/bindings/appbar.xml)
|
||||
content/bindings/flyoutpanel.xml (content/bindings/flyoutpanel.xml)
|
||||
content/bindings/selectionoverlay.xml (content/bindings/selectionoverlay.xml)
|
||||
|
||||
content/prompt/CaptureDialog.xul (content/prompt/CaptureDialog.xul)
|
||||
content/prompt/alert.xul (content/prompt/alert.xul)
|
||||
content/prompt/confirm.xul (content/prompt/confirm.xul)
|
||||
content/prompt/prompt.xul (content/prompt/prompt.xul)
|
||||
content/prompt/promptPassword.xul (content/prompt/promptPassword.xul)
|
||||
content/prompt/select.xul (content/prompt/select.xul)
|
||||
content/prompt/prompt.js (content/prompt/prompt.js)
|
||||
content/prompt/share.xul (content/prompt/share.xul)
|
||||
content/prompt/masterPassword.xul (content/prompt/masterPassword.xul)
|
||||
content/prompt/removeMasterPassword.xul (content/prompt/removeMasterPassword.xul)
|
||||
|
||||
* content/helperui/AlertsHelper.js (content/helperui/AlertsHelper.js)
|
||||
content/helperui/CaptureDialog.js (content/helperui/CaptureDialog.js)
|
||||
content/helperui/CapturePickerUI.js (content/helperui/CapturePickerUI.js)
|
||||
content/helperui/CharsetMenu.js (content/helperui/CharsetMenu.js)
|
||||
content/helperui/IdentityUI.js (content/helperui/IdentityUI.js)
|
||||
content/helperui/IndexedDB.js (content/helperui/IndexedDB.js)
|
||||
content/helperui/MasterPasswordUI.js (content/helperui/MasterPasswordUI.js)
|
||||
content/helperui/MenuUI.js (content/helperui/MenuUI.js)
|
||||
content/helperui/OfflineApps.js (content/helperui/OfflineApps.js)
|
||||
content/helperui/SelectHelperUI.js (content/helperui/SelectHelperUI.js)
|
||||
content/helperui/SelectionHelperUI.js (content/helperui/SelectionHelperUI.js)
|
||||
content/helperui/SharingUI.js (content/helperui/SharingUI.js)
|
||||
content/helperui/FormHelperUI.js (content/helperui/FormHelperUI.js)
|
||||
content/helperui/FindHelperUI.js (content/helperui/FindHelperUI.js)
|
||||
|
||||
content/contenthandlers/ContextMenuHandler.js (content/contenthandlers/ContextMenuHandler.js)
|
||||
content/contenthandlers/PluginCTPHandler.js (content/contenthandlers/PluginCTPHandler.js)
|
||||
content/contenthandlers/SelectionHandler.js (content/contenthandlers/SelectionHandler.js)
|
||||
content/contenthandlers/FormHelper.js (content/contenthandlers/FormHelper.js)
|
||||
content/contenthandlers/FindHandler.js (content/contenthandlers/FindHandler.js)
|
||||
content/contenthandlers/ViewportHandler.js (content/contenthandlers/ViewportHandler.js)
|
||||
content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
|
||||
* content/contenthandlers/Content.js (content/contenthandlers/Content.js)
|
||||
|
||||
content/BrowserTouchHandler.js (content/BrowserTouchHandler.js)
|
||||
* content/WebProgress.js (content/WebProgress.js)
|
||||
content/config.xul (content/config.xul)
|
||||
content/config.js (content/config.js)
|
||||
* content/browser.xul (content/browser.xul)
|
||||
* content/browser.js (content/browser.js)
|
||||
* content/browser-ui.js (content/browser-ui.js)
|
||||
* content/browser-scripts.js (content/browser-scripts.js)
|
||||
* content/ContextCommands.js (content/ContextCommands.js)
|
||||
* content/PageActions.js (content/PageActions.js)
|
||||
content/commandUtil.js (content/commandUtil.js)
|
||||
content/appbar.js (content/appbar.js)
|
||||
content/shell.xul (content/jsshell/shell.xul)
|
||||
content/shell.html (content/jsshell/shell.html)
|
||||
content/browser.css (content/browser.css)
|
||||
content/cursor.css (content/cursor.css)
|
||||
% content branding %content/branding/
|
||||
content/sanitize.js (content/sanitize.js)
|
||||
* content/input.js (content/input.js)
|
||||
* content/Util.js (content/Util.js)
|
||||
content/bookmarks.js (content/bookmarks.js)
|
||||
* content/preferences.js (content/preferences.js)
|
||||
content/exceptions.js (content/exceptions.js)
|
||||
* content/downloads.js (content/downloads.js)
|
||||
content/history.js (content/history.js)
|
||||
content/TopSites.js (content/TopSites.js)
|
||||
content/console.js (content/console.js)
|
||||
content/AnimatedZoom.js (content/AnimatedZoom.js)
|
||||
content/LoginManagerChild.js (content/LoginManagerChild.js)
|
||||
content/video.js (content/video.js)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
content/sync.js (content/sync.js)
|
||||
content/RemoteTabs.js (content/RemoteTabs.js)
|
||||
#endif
|
||||
|
||||
% override chrome://global/content/config.xul chrome://browser/content/config.xul
|
||||
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
|
|
@ -0,0 +1,29 @@
|
|||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_BROWSER_FILES = \
|
||||
head.js \
|
||||
browser_test.js \
|
||||
browser_canonizeURL.js \
|
||||
browser_context_ui.js \
|
||||
browser_onscreen_keyboard.js \
|
||||
browser_onscreen_keyboard.html \
|
||||
browser_remotetabs.js \
|
||||
browser_downloads.js \
|
||||
browser_plugin_input.html \
|
||||
browser_plugin_input_mouse.js \
|
||||
browser_plugin_input_keyboard.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_BROWSER_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/metro/
|
|
@ -0,0 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function install(data, reason) {}
|
||||
function startup(data, reason) {}
|
||||
function shutdown(data, reason) {}
|
||||
function uninstall(data, reason) {}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addon1@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:updateURL>http://example.com/browser/mobile/chrome/tests/browser_upgrade.rdf</em:updateURL>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Install Tests</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addon2@tests.mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Install Tests 2</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addon1@tests.mozilla.org</em:id>
|
||||
<em:version>3.0</em:version>
|
||||
<em:updateURL>http://example.com/browser/mobile/chrome/tests/browser_upgrade.rdf</em:updateURL>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Install Tests</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function install(data, reason) {}
|
||||
function startup(data, reason) {}
|
||||
function shutdown(data, reason) {}
|
||||
function uninstall(data, reason) {}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
locale mozapps te-st chrome # locale
|
||||
locale browser te-st chrome # duplicate locale
|
||||
locale browser te-st-a chrome # second locale
|
||||
locale branding te-st-3 chrome # wrong component
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>locale1@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:type>8</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test Locale</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,21 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
let testcases = [
|
||||
["example", {}, "example"],
|
||||
["example", {ctrlKey: true}, "http://www.example.com/"],
|
||||
["example.org", {ctrlKey: true}, "example.org"],
|
||||
["example", {shiftKey: true}, "http://www.example.net/"],
|
||||
["example", {shiftKey: true, ctrlKey: true}, "http://www.example.org/"],
|
||||
[" example ", {ctrlKey: true}, "http://www.example.com/"],
|
||||
[" example/a ", {ctrlKey: true}, "http://www.example.com/a"]
|
||||
];
|
||||
for (let [input, modifiers, result] of testcases) {
|
||||
is(BrowserUI._canonizeURL(input, modifiers), result, input + " -> " + result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "Context UI on about:start",
|
||||
run: function testAboutStart() {
|
||||
yield addTab("about:start");
|
||||
is(StartUI.isVisible, true, "Start UI is displayed on about:start");
|
||||
is(ContextUI.isVisible, true, "Toolbar is displayed on about:start");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is not displayed initially");
|
||||
is(Elements.appbar.isShowing, false, "Appbar is not displayed initially");
|
||||
|
||||
// toggle on
|
||||
doEdgeUIGesture();
|
||||
is(ContextUI.isVisible, true, "Toolbar is still visible after one swipe");
|
||||
is(ContextUI.isExpanded, true, "Tab bar is visible after one swipe");
|
||||
is(Elements.appbar.isShowing, true, "Appbar is visible after one swipe");
|
||||
|
||||
// toggle off
|
||||
doEdgeUIGesture();
|
||||
is(ContextUI.isVisible, true, "Toolbar is still visible after second swipe");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is hidden after second swipe");
|
||||
is(Elements.appbar.isShowing, false, "Appbar is hidden after second swipe");
|
||||
|
||||
// sanity check - toggle on again
|
||||
doEdgeUIGesture();
|
||||
is(ContextUI.isVisible, true, "Toolbar is still visible after third swipe");
|
||||
is(ContextUI.isExpanded, true, "Tab bar is visible after third swipe");
|
||||
is(Elements.appbar.isShowing, true, "Appbar is visible after third swipe");
|
||||
|
||||
is(StartUI.isVisible, true, "Start UI is still visible");
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "Context UI on a web page (about:)",
|
||||
run: function testAbout() {
|
||||
yield addTab("about:");
|
||||
ContextUI.dismiss();
|
||||
is(StartUI.isVisible, false, "Start UI is not visible on about:");
|
||||
is(ContextUI.isVisible, false, "Toolbar is not initially visible on about:");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is not initially visible on about:");
|
||||
is(Elements.appbar.isShowing, false, "Appbar is not initially visible on about on about::");
|
||||
|
||||
doEdgeUIGesture();
|
||||
is(ContextUI.isVisible, true, "Toolbar is visible after one swipe");
|
||||
is(ContextUI.isExpanded, true, "Tab bar is visble after one swipe");
|
||||
is(Elements.appbar.isShowing, true, "Appbar is visible after one swipe");
|
||||
|
||||
doEdgeUIGesture();
|
||||
is(ContextUI.isVisible, false, "Toolbar is not visible after second swipe");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is not visible after second swipe");
|
||||
is(Elements.appbar.isShowing, false, "Appbar is hidden after second swipe");
|
||||
|
||||
is(StartUI.isVisible, false, "Start UI is still not visible");
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "Control-L keyboard shortcut",
|
||||
run: function testAbout() {
|
||||
let tab = yield addTab("about:");
|
||||
ContextUI.dismiss();
|
||||
is(ContextUI.isVisible, false, "Navbar is not initially visible");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is not initially visible");
|
||||
|
||||
EventUtils.synthesizeKey('l', { accelKey: true });
|
||||
is(ContextUI.isVisible, true, "Navbar is visible");
|
||||
is(ContextUI.isExpanded, false, "Tab bar is not visible");
|
||||
|
||||
let edit = document.getElementById("urlbar-edit");
|
||||
is(edit.value, "about:", "Location field contains the page URL");
|
||||
ok(document.commandDispatcher.focusedElement, edit.inputField, "Location field is focused");
|
||||
is(edit.selectionStart, 0, "Location field is selected");
|
||||
is(edit.selectionEnd, edit.value.length, "Location field is selected");
|
||||
|
||||
Browser.closeTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
function doEdgeUIGesture() {
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("MozEdgeUIGesture", true, false);
|
||||
window.dispatchEvent(event);
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Provides infrastructure for automated download components tests.
|
||||
* (adapted from browser/component/downloads test's head.js)
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
const nsIDM = Ci.nsIDownloadManager;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Test Helpers
|
||||
|
||||
var { spawn } = Task;
|
||||
|
||||
function equalStrings(){
|
||||
let ref = ""+arguments[0];
|
||||
for (let i=1; i<arguments.length; i++){
|
||||
if (ref !== ""+arguments[i]) {
|
||||
info("equalStrings failure: " + ref + " != " + arguments[i]);
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function equalNumbers(){
|
||||
let ref = Number(arguments[0]);
|
||||
for (let i=1; i<arguments.length; i++){
|
||||
if (ref !== Number(arguments[i])) return false;
|
||||
if (ref !== Number(arguments[i])) {
|
||||
info("equalNumbers failure: " + ref + " != " + Number(arguments[i]));
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function waitForMs(aDelay) {
|
||||
let deferred = Promise.defer();
|
||||
let timerID = setTimeout(function(){
|
||||
deferred.resolve(true);
|
||||
}, aDelay || 0);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getPromisedDbResult(aStatement) {
|
||||
let dbConnection = Downloads.manager.DBConnection;
|
||||
let statement = ("string" == typeof aStatement) ?
|
||||
dbConnection.createAsyncStatement(
|
||||
aStatement
|
||||
) : aStatement;
|
||||
|
||||
let deferred = Promise.defer(),
|
||||
resultRows = [],
|
||||
err = null;
|
||||
try {
|
||||
statement.executeAsync({
|
||||
handleResult: function(aResultSet) {
|
||||
let row;
|
||||
if(!aResultSet) {
|
||||
return;
|
||||
}
|
||||
while ((row = aResultSet.getNextRow())){
|
||||
resultRows.push(row);
|
||||
}
|
||||
},
|
||||
handleError: function(aError) {
|
||||
Cu.reportError(aError);
|
||||
err = aError;
|
||||
},
|
||||
handleCompletion: function(){
|
||||
if (err) {
|
||||
deferred.reject(err);
|
||||
} else {
|
||||
deferred.resolve(resultRows);
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
statement.finalize();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
let gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
|
||||
gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
registerCleanupFunction(function () {
|
||||
gTestTargetFile.remove(false);
|
||||
PanelUI.hide();
|
||||
});
|
||||
|
||||
/**
|
||||
* This object contains a property for each column in the downloads table.
|
||||
*/
|
||||
let gDownloadRowTemplate = {
|
||||
name: "test-download.txt",
|
||||
source: "http://www.example.com/test-download.txt",
|
||||
target: NetUtil.newURI(gTestTargetFile).spec,
|
||||
startTime: 1180493839859230,
|
||||
endTime: 1180493839859234,
|
||||
state: nsIDM.DOWNLOAD_FINISHED,
|
||||
currBytes: 0,
|
||||
maxBytes: -1,
|
||||
preferredAction: 0,
|
||||
autoResume: 0
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Test Infrastructure
|
||||
|
||||
function test() {
|
||||
PanelUI.show("downloads-container");
|
||||
runTests();
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// shared test setup
|
||||
function resetDownloads(){
|
||||
var defd = Promise.defer();
|
||||
// do the reset, resolve the defd when done
|
||||
// TODO (sfoster) clear out downloads db, reset relevant state
|
||||
|
||||
let promisedResult = getPromisedDbResult(
|
||||
"DELETE FROM moz_downloads"
|
||||
);
|
||||
return promisedResult.then(function(aResult){
|
||||
// // Reset any prefs that might have been changed.
|
||||
// Services.prefs.clearUserPref("browser.download.panel.shown");
|
||||
|
||||
// Ensure that data is unloaded.
|
||||
let dlMgr = Downloads.manager;
|
||||
let dlsToRemove = [];
|
||||
// Clear all completed/cancelled downloads
|
||||
dlMgr.cleanUp();
|
||||
dlMgr.cleanUpPrivate();
|
||||
|
||||
// Queue up all active ones as well
|
||||
for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) {
|
||||
while (dlsEnum.hasMoreElements()) {
|
||||
dlsToRemove.push(dlsEnum.next());
|
||||
}
|
||||
}
|
||||
// Remove any queued up active downloads
|
||||
dlsToRemove.forEach(function (dl) {
|
||||
dl.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addDownloadRow(aDataRow) {
|
||||
let deferredInsert = Promise.defer();
|
||||
let dataRow = aDataRow;
|
||||
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
let db = dm.DBConnection;
|
||||
|
||||
let columnNames = Object.keys(gDownloadRowTemplate).join(", ");
|
||||
let parameterNames = Object.keys(gDownloadRowTemplate)
|
||||
.map(function(n) ":" + n)
|
||||
.join(", ");
|
||||
|
||||
let statement = db.createAsyncStatement(
|
||||
"INSERT INTO moz_downloads (" + columnNames +
|
||||
", guid) VALUES(" + parameterNames + ", GENERATE_GUID())");
|
||||
|
||||
// Populate insert parameters from the provided data.
|
||||
for (let columnName in gDownloadRowTemplate) {
|
||||
if (!(columnName in dataRow)) {
|
||||
// Update the provided row object with data from the global template,
|
||||
// for columns whose value is not provided explicitly.
|
||||
dataRow[columnName] = gDownloadRowTemplate[columnName];
|
||||
}
|
||||
statement.params[columnName] = dataRow[columnName];
|
||||
}
|
||||
|
||||
// Run the statement asynchronously and wait.
|
||||
let promisedDownloads = getPromisedDbResult(
|
||||
statement
|
||||
);
|
||||
yield promisedDownloads.then(function(){
|
||||
let newItemId = db.lastInsertRowID;
|
||||
let download = dm.getDownload(newItemId);
|
||||
deferredInsert.resolve(download);
|
||||
});
|
||||
}
|
||||
|
||||
function gen_addDownloadRows(aDataRows){
|
||||
if (!aDataRows.length) {
|
||||
yield;
|
||||
}
|
||||
|
||||
try {
|
||||
// Add each of the provided downloads in reverse.
|
||||
for (let i = aDataRows.length - 1; i >= 0; i--) {
|
||||
let dataRow = aDataRows[i];
|
||||
let download = yield addDownloadRow(dataRow);
|
||||
|
||||
// At each iteration, ensure that the start and end time in the global
|
||||
// template is distinct, as these column are used to sort each download
|
||||
// in its category.
|
||||
gDownloadRowTemplate.startTime++;
|
||||
gDownloadRowTemplate.endTime++;
|
||||
}
|
||||
} finally {
|
||||
info("gen_addDownloadRows, finally");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// Test implementations
|
||||
|
||||
/**
|
||||
* Test that:
|
||||
* view can represent all possible download states
|
||||
* clearDownloads does in fact empty the view
|
||||
* addDownload adds an item, to the right place
|
||||
* removeDownload removes an item, leaving the view in the correct state
|
||||
*/
|
||||
gTests.push({
|
||||
desc: "UI sanity check",
|
||||
run: function(){
|
||||
|
||||
ok(document.getElementById('downloads-list'), "Downloads panel grid is present");
|
||||
ok(DownloadsPanelView, "Downloads panel object is present");
|
||||
|
||||
PanelUI.show('downloads-container');
|
||||
ok(DownloadsPanelView.visible, "Downloads panel is visible after being shown");
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "zero downloads",
|
||||
run: function () {
|
||||
|
||||
yield resetDownloads();
|
||||
|
||||
let downloadslist = document.getElementById("downloads-list");
|
||||
|
||||
// wait for the richgrid to announce its readiness
|
||||
// .. fail a test if the timeout is exceeded
|
||||
let isReady = waitForEvent(downloadslist, "DownloadsReady", 2000);
|
||||
// tickle the view to cause it to refresh itself
|
||||
DownloadsPanelView._view.getDownloads();
|
||||
|
||||
yield isReady;
|
||||
|
||||
if (!isReady || isReady instanceof Error){
|
||||
ok(false, "DownloadsReady event never fired");
|
||||
}
|
||||
|
||||
let count = downloadslist.children.length;
|
||||
is(count, 0, "Zero items in grid view with empty downloads db");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Make sure the downloads panel can display items in the right order and
|
||||
* contains the expected data.
|
||||
*/
|
||||
gTests.push({
|
||||
desc: "Show downloads",
|
||||
run: function(){
|
||||
// Display one of each download state.
|
||||
let DownloadData = [
|
||||
{ endTime: 1180493839859239, state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ endTime: 1180493839859238, state: nsIDM.DOWNLOAD_DOWNLOADING },
|
||||
{ endTime: 1180493839859237, state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ endTime: 1180493839859236, state: nsIDM.DOWNLOAD_SCANNING },
|
||||
{ endTime: 1180493839859235, state: nsIDM.DOWNLOAD_QUEUED },
|
||||
{ endTime: 1180493839859234, state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ endTime: 1180493839859233, state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ endTime: 1180493839859232, state: nsIDM.DOWNLOAD_CANCELED },
|
||||
{ endTime: 1180493839859231, state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL },
|
||||
{ endTime: 1180493839859230, state: nsIDM.DOWNLOAD_DIRTY },
|
||||
{ endTime: 1180493839859229, state: nsIDM.DOWNLOAD_BLOCKED_POLICY },
|
||||
];
|
||||
|
||||
yield resetDownloads();
|
||||
DownloadsPanelView._view.getDownloads();
|
||||
|
||||
// NB: beware display limits which might cause mismatch btw. rendered item and db rows
|
||||
|
||||
try {
|
||||
// Populate the downloads database with the data required by this test.
|
||||
// we're going to add stuff to the downloads db.
|
||||
yield spawn( gen_addDownloadRows( DownloadData ) );
|
||||
|
||||
// Test item data and count. This also tests the ordering of the display.
|
||||
let downloadslist = document.getElementById("downloads-list");
|
||||
// wait for the richgrid to announce its readiness
|
||||
// .. fail a test if the timeout is exceeded
|
||||
let isReady = waitForEvent(downloadslist, "DownloadsReady", 2000);
|
||||
|
||||
// tickle the view to cause it to refresh itself
|
||||
DownloadsPanelView._view.getDownloads();
|
||||
|
||||
yield isReady;
|
||||
|
||||
if (!isReady || isReady instanceof Error){
|
||||
ok(false, "DownloadsReady event never fired");
|
||||
}
|
||||
|
||||
is(downloadslist.children.length, DownloadData.length,
|
||||
"There is the correct number of richlistitems");
|
||||
|
||||
for (let i = 0; i < downloadslist.children.length; i++) {
|
||||
let element = downloadslist.children[i];
|
||||
let id = element.getAttribute("downloadId");
|
||||
let dataItem = Downloads.manager.getDownload(id); // nsIDownload object
|
||||
|
||||
ok( equalStrings(
|
||||
element.getAttribute("name"),
|
||||
dataItem.displayName,
|
||||
DownloadData[i].name
|
||||
), "Download names match up");
|
||||
|
||||
ok( equalNumbers(
|
||||
element.getAttribute("state"),
|
||||
dataItem.state,
|
||||
DownloadData[i].state
|
||||
), "Download states match up");
|
||||
|
||||
ok( equalStrings(
|
||||
element.getAttribute("target"),
|
||||
dataItem.target.spec,
|
||||
DownloadData[i].target
|
||||
), "Download targets match up");
|
||||
|
||||
if (DownloadData[i].source && dataItem.referrer){
|
||||
ok( equalStrings(
|
||||
dataItem.referrer.spec,
|
||||
DownloadData[i].source
|
||||
), "Download sources match up");
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
info("Show downloads, some error: " + e);
|
||||
}
|
||||
finally {
|
||||
// Clean up when the test finishes.
|
||||
DownloadsPanelView._view.clearDownloads();
|
||||
yield resetDownloads();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Make sure the downloads can be removed with the expected result on the panel and its listing
|
||||
*/
|
||||
gTests.push({
|
||||
desc: "Remove downloads",
|
||||
run: function(){
|
||||
// Push a few items into the downloads db.
|
||||
let DownloadData = [
|
||||
{ endTime: 1180493839859239, state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ endTime: 1180493839859238, state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ endTime: 1180493839859237, state: nsIDM.DOWNLOAD_FINISHED }
|
||||
];
|
||||
|
||||
yield resetDownloads();
|
||||
DownloadsPanelView._view.getDownloads();
|
||||
|
||||
try {
|
||||
// Populate the downloads database with the data required by this test.
|
||||
yield spawn( gen_addDownloadRows( DownloadData ) );
|
||||
|
||||
// Test item data and count. This also tests the ordering of the display.
|
||||
let downloadslist = document.getElementById("downloads-list");
|
||||
// wait for the richgrid to announce its readiness
|
||||
// .. fail a test if the timeout is exceeded
|
||||
let isReady = waitForEvent(downloadslist, "DownloadsReady", 2000);
|
||||
// tickle the view to cause it to refresh itself
|
||||
DownloadsPanelView._view.getDownloads();
|
||||
|
||||
yield isReady;
|
||||
|
||||
if (!isReady || isReady instanceof Error){
|
||||
is(false, "DownloadsReady event never fired");
|
||||
}
|
||||
|
||||
let downloadRows = null,
|
||||
promisedDownloads;
|
||||
// get all the downloads from the db
|
||||
promisedDownloads = getPromisedDbResult(
|
||||
"SELECT guid "
|
||||
+ "FROM moz_downloads "
|
||||
+ "ORDER BY startTime DESC"
|
||||
).then(function(aRows){
|
||||
downloadRows = aRows;
|
||||
}, function(aError){
|
||||
throw aError;
|
||||
});
|
||||
yield promisedDownloads;
|
||||
|
||||
is(downloadRows.length, 3, "Correct number of downloads in the db before removal");
|
||||
|
||||
// remove the first one
|
||||
let itemNode = downloadslist.children[0];
|
||||
let id = itemNode.getAttribute("downloadId");
|
||||
// check the file exists
|
||||
let download = Downloads.manager.getDownload( id );
|
||||
let file = download.targetFile;
|
||||
ok(file && file.exists());
|
||||
|
||||
Downloads.manager.removeDownload( id );
|
||||
// remove is async(?), wait a bit
|
||||
yield waitForMs(0);
|
||||
|
||||
// get all the downloads from the db
|
||||
downloadRows = null;
|
||||
promisedDownloads = getPromisedDbResult(
|
||||
"SELECT guid "
|
||||
+ "FROM moz_downloads "
|
||||
+ "ORDER BY startTime DESC"
|
||||
).then(function(aRows){
|
||||
downloadRows = aRows;
|
||||
}, function(aError){
|
||||
throw aError;
|
||||
});
|
||||
yield promisedDownloads;
|
||||
|
||||
is(downloadRows.length, 2, "Correct number of downloads in the db after removal");
|
||||
|
||||
is(2, downloadslist.children.length,
|
||||
"Removing a download updates the items list");
|
||||
ok(file && file.exists(), "File still exists after download removal");
|
||||
|
||||
} catch(e) {
|
||||
info("Remove downloads, some error: " + e);
|
||||
}
|
||||
finally {
|
||||
// Clean up when the test finishes.
|
||||
DownloadsPanelView._view.clearDownloads();
|
||||
yield resetDownloads();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>On-Screen Keyboard Test</title>
|
||||
<style>
|
||||
#text { position: absolute; left: 1em; bottom: 1em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input id="text">
|
||||
</body>
|
||||
</head>
|
|
@ -0,0 +1,54 @@
|
|||
/* 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/. */
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "Onscreen keyboard tests",
|
||||
run: function() {
|
||||
// By design, Metro apps can't show the keyboard programmatically, so we
|
||||
// can't use the real keyboard in this test:
|
||||
// http://msdn.microsoft.com/en-us/library/windows/apps/hh465404.aspx#user-driven_invocation
|
||||
//
|
||||
// Instead, we will use this mock object to simulate keyboard changes.
|
||||
let originalUtils = window.MetroUtils;
|
||||
window.MetroUtils = {
|
||||
keyboardHeight: 0,
|
||||
keyboardVisible: false
|
||||
};
|
||||
registerCleanupFunction(function() {
|
||||
window.MetroUtils = originalUtils;
|
||||
});
|
||||
|
||||
let tab = yield addTab(chromeRoot + "browser_onscreen_keyboard.html");
|
||||
// Explicitly dismiss the toolbar at the start, because it messes with the
|
||||
// keyboard and sizing if it's dismissed later.
|
||||
ContextUI.dismiss();
|
||||
|
||||
let doc = tab.browser.contentDocument;
|
||||
let text = doc.getElementById("text")
|
||||
let rect0 = text.getBoundingClientRect();
|
||||
text.focus();
|
||||
|
||||
// "Show" the keyboard.
|
||||
MetroUtils.keyboardHeight = 100;
|
||||
MetroUtils.keyboardVisible = true;
|
||||
Services.obs.notifyObservers(null, "metro_softkeyboard_shown", null);
|
||||
|
||||
let rect1 = text.getBoundingClientRect();
|
||||
is(rect1.top, rect0.top - 100, "text field moves up by 100px");
|
||||
|
||||
// "Hide" the keyboard.
|
||||
MetroUtils.keyboardHeight = 0;
|
||||
MetroUtils.keyboardVisible = false;
|
||||
Services.obs.notifyObservers(null, "metro_softkeyboard_hidden", null);
|
||||
|
||||
let rect2 = text.getBoundingClientRect();
|
||||
is(rect2.top, rect0.top, "text field moves back to the original position");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче