2011-10-26 11:32:14 -07:00
|
|
|
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
|
|
/*
|
|
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is Mozilla Mobile Browser.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Mozilla Corporation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
2011-11-07 17:10:59 -08:00
|
|
|
"use strict";
|
2011-10-26 11:32:14 -07:00
|
|
|
|
2009-03-16 21:27:29 -07:00
|
|
|
let Cc = Components.classes;
|
|
|
|
let Ci = Components.interfaces;
|
|
|
|
let Cu = Components.utils;
|
2010-11-11 10:48:00 -08:00
|
|
|
let Cr = Components.results;
|
2008-04-18 06:41:49 -07:00
|
|
|
|
2011-10-10 17:40:17 -07:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm")
|
2011-11-21 22:12:59 -08:00
|
|
|
Cu.import("resource://gre/modules/AddonManager.jsm");
|
2010-05-21 10:00:27 -07:00
|
|
|
|
2011-11-27 06:49:00 -08:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "PluralForm", function() {
|
|
|
|
Cu.import("resource://gre/modules/PluralForm.jsm");
|
|
|
|
return PluralForm;
|
|
|
|
});
|
|
|
|
|
2012-03-01 10:58:19 -08:00
|
|
|
// Lazily-loaded browser scripts:
|
|
|
|
[
|
|
|
|
["SelectHelper", "chrome://browser/content/SelectHelper.js"],
|
|
|
|
].forEach(function (aScript) {
|
|
|
|
let [name, script] = aScript;
|
|
|
|
XPCOMUtils.defineLazyGetter(window, name, function() {
|
|
|
|
let sandbox = {};
|
|
|
|
Services.scriptloader.loadSubScript(script, sandbox);
|
|
|
|
return sandbox[name];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2011-12-08 12:42:55 -08:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "Haptic",
|
|
|
|
"@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback");
|
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
|
|
|
|
"@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
|
2011-12-08 12:42:55 -08:00
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
const kStateActive = 0x00000001; // :active pseudoclass for elements
|
2011-10-26 11:32:14 -07:00
|
|
|
|
2012-01-06 11:44:33 -08:00
|
|
|
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
|
|
|
|
|
2011-10-19 09:17:59 -07:00
|
|
|
// TODO: Take into account ppi in these units?
|
|
|
|
|
2011-10-17 12:02:27 -07:00
|
|
|
// The ratio of velocity that is retained every ms.
|
|
|
|
const kPanDeceleration = 0.999;
|
|
|
|
|
|
|
|
// The number of ms to consider events over for a swipe gesture.
|
2011-10-19 09:17:59 -07:00
|
|
|
const kSwipeLength = 500;
|
|
|
|
|
|
|
|
// The number of pixels to move before we consider a drag to be more than
|
|
|
|
// just a click with jitter.
|
|
|
|
const kDragThreshold = 10;
|
|
|
|
|
|
|
|
// The number of pixels to move to break out of axis-lock
|
|
|
|
const kLockBreakThreshold = 100;
|
2011-10-17 12:02:27 -07:00
|
|
|
|
|
|
|
// Minimum speed to move during kinetic panning. 0.015 pixels/ms is roughly
|
|
|
|
// equivalent to a pixel every 4 frames at 60fps.
|
|
|
|
const kMinKineticSpeed = 0.015;
|
|
|
|
|
2011-10-19 09:17:59 -07:00
|
|
|
// Maximum kinetic panning speed. 9 pixels/ms is equivalent to 150 pixels per
|
2011-10-17 12:02:27 -07:00
|
|
|
// frame at 60fps.
|
2011-10-19 09:17:59 -07:00
|
|
|
const kMaxKineticSpeed = 9;
|
2011-10-17 12:02:27 -07:00
|
|
|
|
|
|
|
// The maximum magnitude of disparity allowed between axes acceleration. If
|
|
|
|
// it's larger than this, lock the slow-moving axis.
|
|
|
|
const kAxisLockRatio = 5;
|
|
|
|
|
2011-11-09 17:39:29 -08:00
|
|
|
// The element tag names that are considered to receive input. Mouse-down
|
|
|
|
// events directed to one of these are allowed to go through.
|
|
|
|
const kElementsReceivingInput = {
|
|
|
|
applet: true,
|
|
|
|
audio: true,
|
|
|
|
button: true,
|
|
|
|
embed: true,
|
|
|
|
input: true,
|
|
|
|
map: true,
|
|
|
|
select: true,
|
|
|
|
textarea: true,
|
|
|
|
video: true
|
|
|
|
};
|
|
|
|
|
2011-10-10 17:40:17 -07:00
|
|
|
function dump(a) {
|
2011-10-12 14:31:04 -07:00
|
|
|
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
|
|
|
|
}
|
|
|
|
|
2011-11-18 18:07:14 -08:00
|
|
|
function getBridge() {
|
|
|
|
return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
|
|
|
|
}
|
|
|
|
|
2011-10-13 13:47:29 -07:00
|
|
|
function sendMessageToJava(aMessage) {
|
2011-11-18 18:07:14 -08:00
|
|
|
return getBridge().handleGeckoMessage(JSON.stringify(aMessage));
|
2009-07-17 16:17:57 -07:00
|
|
|
}
|
|
|
|
|
2011-10-25 05:29:30 -07:00
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2010-05-21 10:00:27 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
|
|
|
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
|
|
|
|
#endif
|
|
|
|
|
2011-12-19 08:44:48 -08:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
|
|
|
|
let ContentAreaUtils = {};
|
|
|
|
Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
|
|
|
|
return ContentAreaUtils;
|
|
|
|
});
|
|
|
|
|
2012-03-05 09:06:50 -08:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "Rect", function() {
|
|
|
|
Cu.import("resource://gre/modules/Geometry.jsm");
|
|
|
|
return Rect;
|
|
|
|
});
|
|
|
|
|
2011-10-24 14:08:21 -07:00
|
|
|
function resolveGeckoURI(aURI) {
|
|
|
|
if (aURI.indexOf("chrome://") == 0) {
|
|
|
|
let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
|
|
|
|
return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;
|
|
|
|
} else if (aURI.indexOf("resource://") == 0) {
|
|
|
|
let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
|
|
|
|
return handler.resolveURI(Services.io.newURI(aURI, null, null));
|
|
|
|
}
|
|
|
|
return aURI;
|
|
|
|
}
|
2011-10-13 13:06:41 -07:00
|
|
|
|
2011-10-26 13:21:51 -07:00
|
|
|
/**
|
|
|
|
* Cache of commonly used string bundles.
|
|
|
|
*/
|
|
|
|
var Strings = {};
|
|
|
|
[
|
|
|
|
["brand", "chrome://branding/locale/brand.properties"],
|
2012-01-23 13:47:48 -08:00
|
|
|
["browser", "chrome://browser/locale/browser.properties"],
|
2012-02-14 15:32:45 -08:00
|
|
|
["charset", "chrome://global/locale/charsetTitles.properties"]
|
2011-10-26 13:21:51 -07:00
|
|
|
].forEach(function (aStringBundle) {
|
|
|
|
let [name, bundle] = aStringBundle;
|
|
|
|
XPCOMUtils.defineLazyGetter(Strings, name, function() {
|
|
|
|
return Services.strings.createBundle(bundle);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2012-01-03 22:54:51 -08:00
|
|
|
var MetadataProvider = {
|
|
|
|
getDrawMetadata: function getDrawMetadata() {
|
|
|
|
return BrowserApp.getDrawMetadata();
|
|
|
|
},
|
|
|
|
|
|
|
|
paintingSuppressed: function paintingSuppressed() {
|
2012-01-06 16:42:46 -08:00
|
|
|
// Get the current tab. Don't suppress painting if there are no tabs yet.
|
|
|
|
let tab = BrowserApp.selectedTab;
|
|
|
|
if (!tab)
|
2012-01-03 22:54:51 -08:00
|
|
|
return false;
|
2012-01-06 16:42:46 -08:00
|
|
|
|
|
|
|
// If the viewport metadata has not yet been updated (and therefore the browser size has not
|
|
|
|
// been changed accordingly), do not draw yet. We'll get an unsightly flash on page transitions
|
|
|
|
// otherwise, because we receive a paint event after the new document is shown but before the
|
|
|
|
// correct browser size for the new document has been set.
|
|
|
|
//
|
|
|
|
// This whole situation exists because the docshell and the browser element are unaware of the
|
|
|
|
// existence of <meta viewport>. Therefore they dispatch paint events without knowledge of the
|
|
|
|
// invariant that the page must not be drawn until the browser size has been appropriately set.
|
|
|
|
// It would be easier if the docshell were made aware of the existence of <meta viewport> so
|
|
|
|
// that this logic could be removed.
|
|
|
|
|
|
|
|
let viewportDocumentId = tab.documentIdForCurrentViewport;
|
|
|
|
let contentDocumentId = ViewportHandler.getIdForDocument(tab.browser.contentDocument);
|
|
|
|
if (viewportDocumentId != null && viewportDocumentId != contentDocumentId)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Suppress painting if the current presentation shell is suppressing painting.
|
|
|
|
let cwu = tab.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
2012-01-03 22:54:51 -08:00
|
|
|
return cwu.paintingSuppressed;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
var BrowserApp = {
|
|
|
|
_tabs: [],
|
|
|
|
_selectedTab: null,
|
|
|
|
|
|
|
|
deck: null,
|
|
|
|
|
|
|
|
startup: function startup() {
|
|
|
|
window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
|
|
|
|
dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
|
|
|
|
|
|
|
|
this.deck = document.getElementById("browsers");
|
|
|
|
BrowserEventHandler.init();
|
2011-11-29 09:35:26 -08:00
|
|
|
ViewportHandler.init();
|
2011-10-13 13:06:41 -07:00
|
|
|
|
2012-01-03 22:54:51 -08:00
|
|
|
getBridge().setDrawMetadataProvider(MetadataProvider);
|
2011-11-18 18:07:14 -08:00
|
|
|
|
2012-02-17 10:52:26 -08:00
|
|
|
getBridge().browserApp = this;
|
|
|
|
|
2011-10-24 12:20:27 -07:00
|
|
|
Services.obs.addObserver(this, "Tab:Add", false);
|
|
|
|
Services.obs.addObserver(this, "Tab:Load", false);
|
2012-01-24 09:15:52 -08:00
|
|
|
Services.obs.addObserver(this, "Tab:Selected", false);
|
2012-01-24 09:15:41 -08:00
|
|
|
Services.obs.addObserver(this, "Tab:Closed", false);
|
2011-10-31 08:02:34 -07:00
|
|
|
Services.obs.addObserver(this, "Session:Back", false);
|
|
|
|
Services.obs.addObserver(this, "Session:Forward", false);
|
|
|
|
Services.obs.addObserver(this, "Session:Reload", false);
|
2011-11-16 16:23:22 -08:00
|
|
|
Services.obs.addObserver(this, "Session:Stop", false);
|
2011-10-25 08:51:23 -07:00
|
|
|
Services.obs.addObserver(this, "SaveAs:PDF", false);
|
2011-11-09 17:31:09 -08:00
|
|
|
Services.obs.addObserver(this, "Browser:Quit", false);
|
2011-10-14 12:48:02 -07:00
|
|
|
Services.obs.addObserver(this, "Preferences:Get", false);
|
|
|
|
Services.obs.addObserver(this, "Preferences:Set", false);
|
2011-10-26 11:30:48 -07:00
|
|
|
Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
|
2011-10-27 21:55:15 -07:00
|
|
|
Services.obs.addObserver(this, "Sanitize:ClearAll", false);
|
2011-11-09 17:39:29 -08:00
|
|
|
Services.obs.addObserver(this, "PanZoom:PanZoom", false);
|
2011-11-08 10:36:04 -08:00
|
|
|
Services.obs.addObserver(this, "FullScreen:Exit", false);
|
2011-11-18 18:07:14 -08:00
|
|
|
Services.obs.addObserver(this, "Viewport:Change", false);
|
2011-12-07 17:52:07 -08:00
|
|
|
Services.obs.addObserver(this, "SearchEngines:Get", false);
|
2011-12-16 15:11:09 -08:00
|
|
|
Services.obs.addObserver(this, "Passwords:Init", false);
|
2012-02-27 10:10:14 -08:00
|
|
|
Services.obs.addObserver(this, "FormHistory:Init", false);
|
2011-10-13 13:06:41 -07:00
|
|
|
|
2012-02-14 09:50:55 -08:00
|
|
|
Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
|
|
|
|
|
2011-11-15 12:30:58 -08:00
|
|
|
function showFullScreenWarning() {
|
|
|
|
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short");
|
|
|
|
}
|
|
|
|
|
2011-11-08 10:36:04 -08:00
|
|
|
window.addEventListener("fullscreen", function() {
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: window.fullScreen ? "ToggleChrome:Show" : "ToggleChrome:Hide"
|
2011-11-29 09:42:43 -08:00
|
|
|
}
|
2011-11-08 10:36:04 -08:00
|
|
|
});
|
2012-01-26 09:17:33 -08:00
|
|
|
}, false);
|
|
|
|
|
|
|
|
window.addEventListener("mozfullscreenchange", function() {
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: document.mozFullScreen ? "DOMFullScreen:Start" : "DOMFullScreen:Stop"
|
|
|
|
}
|
|
|
|
});
|
2011-11-08 10:36:06 -08:00
|
|
|
|
2012-01-26 09:17:33 -08:00
|
|
|
if (document.mozFullScreen)
|
2011-11-15 12:30:58 -08:00
|
|
|
showFullScreenWarning();
|
2011-11-08 10:36:04 -08:00
|
|
|
}, false);
|
|
|
|
|
2011-11-15 12:30:58 -08:00
|
|
|
// When a restricted key is pressed in DOM full-screen mode, we should display
|
|
|
|
// the "Press ESC to exit" warning message.
|
|
|
|
window.addEventListener("MozShowFullScreenWarning", showFullScreenWarning, true);
|
|
|
|
|
2011-10-20 22:50:04 -07:00
|
|
|
NativeWindow.init();
|
2011-10-26 21:29:46 -07:00
|
|
|
Downloads.init();
|
2011-12-06 16:44:08 -08:00
|
|
|
FormAssistant.init();
|
2011-11-11 08:02:10 -08:00
|
|
|
OfflineApps.init();
|
2011-11-15 14:45:10 -08:00
|
|
|
IndexedDB.init();
|
2011-11-21 22:12:59 -08:00
|
|
|
XPInstallObserver.init();
|
2011-11-30 14:37:59 -08:00
|
|
|
ConsoleAPI.init();
|
2011-12-13 16:17:56 -08:00
|
|
|
ClipboardHelper.init();
|
2011-12-17 13:50:09 -08:00
|
|
|
PermissionsHelper.init();
|
2012-01-23 13:47:48 -08:00
|
|
|
CharacterEncoding.init();
|
2011-10-26 21:29:46 -07:00
|
|
|
|
|
|
|
// Init LoginManager
|
|
|
|
Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
2011-12-06 16:44:08 -08:00
|
|
|
// Init FormHistory
|
|
|
|
Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
|
2011-10-20 22:50:04 -07:00
|
|
|
|
2012-01-03 22:21:49 -08:00
|
|
|
let url = "about:home";
|
2012-01-27 22:04:54 -08:00
|
|
|
let forceRestore = false;
|
2012-01-09 11:09:53 -08:00
|
|
|
if ("arguments" in window) {
|
|
|
|
if (window.arguments[0])
|
|
|
|
url = window.arguments[0];
|
|
|
|
if (window.arguments[1])
|
2012-01-27 22:04:54 -08:00
|
|
|
forceRestore = window.arguments[1];
|
2012-01-20 06:36:08 -08:00
|
|
|
if (window.arguments[2])
|
|
|
|
gScreenWidth = window.arguments[2];
|
|
|
|
if (window.arguments[3])
|
|
|
|
gScreenHeight = window.arguments[3];
|
2012-01-09 11:09:53 -08:00
|
|
|
}
|
2011-10-15 13:27:03 -07:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
// XXX maybe we don't do this if the launch was kicked off from external
|
|
|
|
Services.io.offline = false;
|
2011-10-19 11:17:00 -07:00
|
|
|
|
2011-10-24 17:34:17 -07:00
|
|
|
// Broadcast a UIReady message so add-ons know we are finished with startup
|
|
|
|
let event = document.createEvent("Events");
|
|
|
|
event.initEvent("UIReady", true, false);
|
|
|
|
window.dispatchEvent(event);
|
2011-10-25 16:12:44 -07:00
|
|
|
|
2011-12-20 17:41:45 -08:00
|
|
|
// restore the previous session
|
|
|
|
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
|
2012-01-27 22:04:54 -08:00
|
|
|
if (forceRestore || ss.shouldRestore()) {
|
2012-01-03 22:21:49 -08:00
|
|
|
// A restored tab should not be active if we are loading a URL
|
|
|
|
let restoreToFront = false;
|
|
|
|
|
2012-02-01 14:25:50 -08:00
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Session:RestoreBegin"
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-01-03 22:21:49 -08:00
|
|
|
// Open any commandline URLs, except the homepage
|
|
|
|
if (url && url != "about:home") {
|
|
|
|
this.addTab(url);
|
|
|
|
} else {
|
|
|
|
// Let the session make a restored tab active
|
|
|
|
restoreToFront = true;
|
2012-01-27 22:04:54 -08:00
|
|
|
}
|
2012-01-03 22:21:49 -08:00
|
|
|
|
2012-01-27 22:04:54 -08:00
|
|
|
// Be ready to handle any restore failures by making sure we have a valid tab opened
|
|
|
|
let restoreCleanup = {
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
Services.obs.removeObserver(restoreCleanup, "sessionstore-windows-restored");
|
|
|
|
if (aData == "fail") {
|
2012-02-01 17:51:41 -08:00
|
|
|
BrowserApp.addTab("about:home", {
|
|
|
|
showProgress: false,
|
|
|
|
selected: restoreToFront
|
|
|
|
});
|
2012-01-03 22:21:49 -08:00
|
|
|
}
|
2012-02-01 14:25:50 -08:00
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Session:RestoreEnd"
|
|
|
|
}
|
|
|
|
});
|
2012-01-27 22:04:54 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
Services.obs.addObserver(restoreCleanup, "sessionstore-windows-restored", false);
|
2012-01-03 22:21:49 -08:00
|
|
|
|
|
|
|
// Start the restore
|
2012-01-27 22:04:54 -08:00
|
|
|
ss.restoreLastSession(restoreToFront, forceRestore);
|
2012-01-03 22:21:49 -08:00
|
|
|
} else {
|
2012-01-26 15:07:38 -08:00
|
|
|
this.addTab(url, { showProgress: url != "about:home" });
|
2012-01-09 11:51:07 -08:00
|
|
|
|
|
|
|
// show telemetry door hanger if we aren't restoring a session
|
|
|
|
this._showTelemetryPrompt();
|
2012-01-03 22:21:49 -08:00
|
|
|
}
|
2011-12-20 17:41:45 -08:00
|
|
|
|
2011-10-25 16:12:44 -07:00
|
|
|
// notify java that gecko has loaded
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Gecko:Ready"
|
|
|
|
}
|
|
|
|
});
|
2012-02-03 12:35:01 -08:00
|
|
|
|
|
|
|
// after gecko has loaded, set the checkerboarding pref once at startup (for testing only)
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
"type": "Checkerboard:Toggle",
|
|
|
|
"value": Services.prefs.getBoolPref("gfx.show_checkerboard_pattern")
|
|
|
|
}
|
|
|
|
});
|
2012-01-09 11:51:07 -08:00
|
|
|
},
|
2011-11-11 12:31:16 -08:00
|
|
|
|
2012-01-09 11:51:07 -08:00
|
|
|
_showTelemetryPrompt: function _showTelemetryPrompt() {
|
2012-02-10 06:44:06 -08:00
|
|
|
const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted";
|
|
|
|
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
|
|
|
|
const PREF_TELEMETRY_REJECTED = "toolkit.telemetry.rejected";
|
|
|
|
|
|
|
|
// This is used to reprompt users when privacy message changes
|
|
|
|
const TELEMETRY_PROMPT_REV = 2;
|
|
|
|
|
|
|
|
let telemetryPrompted = null;
|
2011-11-11 12:31:16 -08:00
|
|
|
try {
|
2012-02-10 06:44:06 -08:00
|
|
|
telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED);
|
2012-01-09 11:51:07 -08:00
|
|
|
} catch (e) { /* Optional */ }
|
2012-02-10 06:44:06 -08:00
|
|
|
|
|
|
|
// If the user has seen the latest telemetry prompt, do not prompt again
|
|
|
|
// else clear old prefs and reprompt
|
|
|
|
if (telemetryPrompted === TELEMETRY_PROMPT_REV)
|
2012-01-09 11:51:07 -08:00
|
|
|
return;
|
2011-11-11 12:31:16 -08:00
|
|
|
|
2012-02-10 06:44:06 -08:00
|
|
|
Services.prefs.clearUserPref(PREF_TELEMETRY_PROMPTED);
|
|
|
|
Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
|
|
|
|
|
2012-01-09 11:51:07 -08:00
|
|
|
let buttons = [
|
|
|
|
{
|
|
|
|
label: Strings.browser.GetStringFromName("telemetry.optin.yes"),
|
|
|
|
callback: function () {
|
2012-02-10 06:44:06 -08:00
|
|
|
Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
|
|
|
|
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
|
2012-01-09 11:51:07 -08:00
|
|
|
}
|
2012-01-09 11:51:07 -08:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: Strings.browser.GetStringFromName("telemetry.optin.no"),
|
|
|
|
callback: function () {
|
2012-02-10 06:44:06 -08:00
|
|
|
Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
|
|
|
|
Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true);
|
2012-01-09 11:51:07 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
];
|
2012-02-10 06:44:06 -08:00
|
|
|
|
2012-01-09 11:51:07 -08:00
|
|
|
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
|
|
|
|
let message = Strings.browser.formatStringFromName("telemetry.optin.message", [brandShortName], 1);
|
|
|
|
NativeWindow.doorhanger.show(message, "telemetry-optin", buttons);
|
2011-10-13 11:13:41 -07:00
|
|
|
},
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
shutdown: function shutdown() {
|
2011-10-20 22:50:04 -07:00
|
|
|
NativeWindow.uninit();
|
2011-12-06 16:44:08 -08:00
|
|
|
FormAssistant.uninit();
|
2011-11-11 08:02:10 -08:00
|
|
|
OfflineApps.uninit();
|
2011-11-15 14:45:10 -08:00
|
|
|
IndexedDB.uninit();
|
2011-11-29 09:35:26 -08:00
|
|
|
ViewportHandler.uninit();
|
2011-11-21 22:12:59 -08:00
|
|
|
XPInstallObserver.uninit();
|
2011-11-30 14:37:59 -08:00
|
|
|
ConsoleAPI.uninit();
|
2012-01-23 13:47:48 -08:00
|
|
|
CharacterEncoding.uninit();
|
2011-10-13 11:13:41 -07:00
|
|
|
},
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
get tabs() {
|
|
|
|
return this._tabs;
|
|
|
|
},
|
2011-10-10 17:40:17 -07:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
get selectedTab() {
|
|
|
|
return this._selectedTab;
|
|
|
|
},
|
2011-10-10 17:40:17 -07:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
set selectedTab(aTab) {
|
2012-01-31 09:35:23 -08:00
|
|
|
if (this._selectedTab)
|
|
|
|
this._selectedTab.setActive(false);
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
this._selectedTab = aTab;
|
|
|
|
if (!aTab)
|
|
|
|
return;
|
2010-09-22 16:27:11 -07:00
|
|
|
|
2012-01-31 09:35:23 -08:00
|
|
|
aTab.setActive(true);
|
2011-12-01 06:36:41 -08:00
|
|
|
aTab.updateViewport(false);
|
2011-11-23 11:07:29 -08:00
|
|
|
this.deck.selectedPanel = aTab.vbox;
|
2011-10-13 13:06:41 -07:00
|
|
|
},
|
2009-08-05 14:06:38 -07:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
get selectedBrowser() {
|
|
|
|
if (this._selectedTab)
|
|
|
|
return this._selectedTab.browser;
|
|
|
|
return null;
|
|
|
|
},
|
2010-11-01 16:36:07 -07:00
|
|
|
|
2011-10-14 22:35:15 -07:00
|
|
|
getTabForId: function getTabForId(aId) {
|
|
|
|
let tabs = this._tabs;
|
|
|
|
for (let i=0; i < tabs.length; i++) {
|
|
|
|
if (tabs[i].id == aId)
|
|
|
|
return tabs[i];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
getTabForBrowser: function getTabForBrowser(aBrowser) {
|
|
|
|
let tabs = this._tabs;
|
|
|
|
for (let i = 0; i < tabs.length; i++) {
|
|
|
|
if (tabs[i].browser == aBrowser)
|
|
|
|
return tabs[i];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
getBrowserForWindow: function getBrowserForWindow(aWindow) {
|
|
|
|
let tabs = this._tabs;
|
|
|
|
for (let i = 0; i < tabs.length; i++) {
|
|
|
|
if (tabs[i].browser.contentWindow == aWindow)
|
|
|
|
return tabs[i].browser;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
getBrowserForDocument: function getBrowserForDocument(aDocument) {
|
|
|
|
let tabs = this._tabs;
|
|
|
|
for (let i = 0; i < tabs.length; i++) {
|
|
|
|
if (tabs[i].browser.contentDocument == aDocument)
|
|
|
|
return tabs[i].browser;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
2010-11-16 14:50:34 -08:00
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
loadURI: function loadURI(aURI, aBrowser, aParams) {
|
|
|
|
aBrowser = aBrowser || this.selectedBrowser;
|
|
|
|
if (!aBrowser)
|
2011-10-13 13:06:41 -07:00
|
|
|
return;
|
2010-11-16 14:50:34 -08:00
|
|
|
|
2011-12-18 22:28:44 -08:00
|
|
|
aParams = aParams || {};
|
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
2011-10-13 13:06:41 -07:00
|
|
|
let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
|
|
|
|
let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
|
|
|
|
let charset = "charset" in aParams ? aParams.charset : null;
|
2011-12-16 11:32:32 -08:00
|
|
|
|
2012-01-19 11:44:16 -08:00
|
|
|
if ("showProgress" in aParams) {
|
|
|
|
let tab = this.getTabForBrowser(aBrowser);
|
|
|
|
if (tab)
|
|
|
|
tab.showProgress = aParams.showProgress;
|
|
|
|
}
|
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
try {
|
|
|
|
aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
|
|
|
|
} catch(e) {
|
|
|
|
let tab = this.getTabForBrowser(aBrowser);
|
|
|
|
if (tab) {
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "Content:LoadError",
|
|
|
|
tabID: tab.id,
|
|
|
|
uri: aBrowser.currentURI.spec,
|
|
|
|
title: aBrowser.contentTitle
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
|
|
|
dump("Handled load error: " + e)
|
|
|
|
}
|
|
|
|
}
|
2011-10-13 13:06:41 -07:00
|
|
|
},
|
|
|
|
|
2011-11-08 12:49:25 -08:00
|
|
|
addTab: function addTab(aURI, aParams) {
|
2012-01-26 15:07:38 -08:00
|
|
|
aParams = aParams || {};
|
|
|
|
|
2011-11-08 12:49:25 -08:00
|
|
|
let newTab = new Tab(aURI, aParams);
|
2011-10-13 13:06:41 -07:00
|
|
|
this._tabs.push(newTab);
|
2012-01-26 15:07:38 -08:00
|
|
|
|
2012-01-31 09:35:23 -08:00
|
|
|
let selected = "selected" in aParams ? aParams.selected : true;
|
|
|
|
if (selected)
|
|
|
|
this.selectedTab = newTab;
|
2011-12-02 08:12:47 -08:00
|
|
|
|
2011-12-20 17:41:45 -08:00
|
|
|
let evt = document.createEvent("UIEvents");
|
|
|
|
evt.initUIEvent("TabOpen", true, false, window, null);
|
|
|
|
newTab.browser.dispatchEvent(evt);
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
return newTab;
|
|
|
|
},
|
|
|
|
|
2012-01-24 09:15:41 -08:00
|
|
|
// Use this method to close a tab from JS. This method sends a message
|
|
|
|
// to Java to close the tab in the Java UI (we'll get a Tab:Closed message
|
|
|
|
// back from Java when that happens).
|
2012-01-20 19:19:53 -08:00
|
|
|
closeTab: function closeTab(aTab) {
|
2012-01-24 09:15:41 -08:00
|
|
|
if (!aTab) {
|
|
|
|
Cu.reportError("Error trying to close tab (tab doesn't exist)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "Tab:Close",
|
|
|
|
tabID: aTab.id
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Calling this will update the state in BrowserApp after a tab has been
|
|
|
|
// closed in the Java UI.
|
|
|
|
_handleTabClosed: function _handleTabClosed(aTab) {
|
2011-10-13 13:06:41 -07:00
|
|
|
if (aTab == this.selectedTab)
|
|
|
|
this.selectedTab = null;
|
|
|
|
|
2011-12-20 17:41:45 -08:00
|
|
|
let evt = document.createEvent("UIEvents");
|
|
|
|
evt.initUIEvent("TabClose", true, false, window, null);
|
|
|
|
aTab.browser.dispatchEvent(evt);
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
aTab.destroy();
|
|
|
|
this._tabs.splice(this._tabs.indexOf(aTab), 1);
|
|
|
|
},
|
|
|
|
|
2012-01-24 09:15:52 -08:00
|
|
|
// Use this method to select a tab from JS. This method sends a message
|
|
|
|
// to Java to select the tab in the Java UI (we'll get a Tab:Selected message
|
|
|
|
// back from Java when that happens).
|
2011-10-24 12:20:27 -07:00
|
|
|
selectTab: function selectTab(aTab) {
|
2012-01-24 09:15:52 -08:00
|
|
|
if (!aTab) {
|
|
|
|
Cu.reportError("Error trying to select tab (tab doesn't exist)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "Tab:Select",
|
|
|
|
tabID: aTab.id
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
|
|
|
},
|
|
|
|
|
|
|
|
// This method updates the state in BrowserApp after a tab has been selected
|
|
|
|
// in the Java UI.
|
|
|
|
_handleTabSelected: function _handleTabSelected(aTab) {
|
2012-01-31 09:35:23 -08:00
|
|
|
this.selectedTab = aTab;
|
2011-10-20 22:50:04 -07:00
|
|
|
|
2012-01-31 09:35:23 -08:00
|
|
|
let evt = document.createEvent("UIEvents");
|
|
|
|
evt.initUIEvent("TabSelect", true, false, window, null);
|
|
|
|
aTab.browser.dispatchEvent(evt);
|
2012-01-25 20:06:08 -08:00
|
|
|
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "Tab:Selected:Done",
|
|
|
|
tabID: aTab.id
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
2011-10-14 22:35:15 -07:00
|
|
|
},
|
|
|
|
|
2011-11-16 14:33:43 -08:00
|
|
|
quit: function quit() {
|
2012-01-09 11:56:30 -08:00
|
|
|
// Figure out if there's at least one other browser window around.
|
|
|
|
let lastBrowser = true;
|
|
|
|
let e = Services.wm.getEnumerator("navigator:browser");
|
|
|
|
while (e.hasMoreElements() && lastBrowser) {
|
|
|
|
let win = e.getNext();
|
|
|
|
if (win != window)
|
|
|
|
lastBrowser = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastBrowser) {
|
|
|
|
// Let everyone know we are closing the last browser window
|
|
|
|
let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
|
|
|
|
Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null);
|
|
|
|
if (closingCanceled.data)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null);
|
|
|
|
}
|
|
|
|
|
|
|
|
window.QueryInterface(Ci.nsIDOMChromeWindow).minimize();
|
|
|
|
window.close();
|
2011-11-16 14:33:43 -08:00
|
|
|
},
|
|
|
|
|
2011-10-25 08:51:23 -07:00
|
|
|
saveAsPDF: function saveAsPDF(aBrowser) {
|
|
|
|
// Create the final destination file location
|
2012-01-24 09:16:56 -08:00
|
|
|
let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null);
|
2011-10-25 08:51:23 -07:00
|
|
|
fileName = fileName.trim() + ".pdf";
|
|
|
|
|
|
|
|
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
|
|
|
let downloadsDir = dm.defaultDownloadsDirectory;
|
|
|
|
|
|
|
|
let file = downloadsDir.clone();
|
|
|
|
file.append(fileName);
|
2011-11-07 17:10:59 -08:00
|
|
|
file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8));
|
2011-10-25 08:51:23 -07:00
|
|
|
|
|
|
|
let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService).newPrintSettings;
|
|
|
|
printSettings.printSilent = true;
|
|
|
|
printSettings.showPrintProgress = false;
|
|
|
|
printSettings.printBGImages = true;
|
|
|
|
printSettings.printBGColors = true;
|
|
|
|
printSettings.printToFile = true;
|
|
|
|
printSettings.toFileName = file.path;
|
|
|
|
printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
|
|
|
|
printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
|
|
|
|
|
|
|
//XXX we probably need a preference here, the header can be useful
|
|
|
|
printSettings.footerStrCenter = "";
|
|
|
|
printSettings.footerStrLeft = "";
|
|
|
|
printSettings.footerStrRight = "";
|
|
|
|
printSettings.headerStrCenter = "";
|
|
|
|
printSettings.headerStrLeft = "";
|
|
|
|
printSettings.headerStrRight = "";
|
|
|
|
|
2012-01-12 12:16:31 -08:00
|
|
|
// Create a valid mimeInfo for the PDF
|
|
|
|
let ms = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
|
|
|
let mimeInfo = ms.getFromTypeAndExtension("application/pdf", "pdf");
|
|
|
|
|
2012-01-18 12:53:12 -08:00
|
|
|
let webBrowserPrint = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebBrowserPrint);
|
2011-10-25 08:51:23 -07:00
|
|
|
|
2011-11-22 16:46:10 -08:00
|
|
|
let cancelable = {
|
|
|
|
cancel: function (aReason) {
|
|
|
|
webBrowserPrint.cancel();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let download = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD,
|
|
|
|
aBrowser.currentURI,
|
2012-01-12 12:16:31 -08:00
|
|
|
Services.io.newFileURI(file), "", mimeInfo,
|
2011-11-22 16:46:10 -08:00
|
|
|
Date.now() * 1000, null, cancelable);
|
2011-10-25 08:51:23 -07:00
|
|
|
|
2011-11-22 16:46:10 -08:00
|
|
|
webBrowserPrint.print(printSettings, download);
|
2011-10-25 08:51:23 -07:00
|
|
|
},
|
|
|
|
|
2011-10-14 12:48:02 -07:00
|
|
|
getPreferences: function getPreferences(aPrefNames) {
|
|
|
|
try {
|
|
|
|
let json = JSON.parse(aPrefNames);
|
|
|
|
let prefs = [];
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-10-14 12:48:02 -07:00
|
|
|
for each (let prefName in json) {
|
|
|
|
let pref = {
|
2011-10-25 19:51:44 -07:00
|
|
|
name: prefName
|
2011-10-14 12:48:02 -07:00
|
|
|
};
|
|
|
|
|
2011-12-13 14:28:45 -08:00
|
|
|
// The plugin pref is actually two separate prefs, so
|
|
|
|
// we need to handle it differently
|
|
|
|
if (prefName == "plugin.enable") {
|
|
|
|
// Use a string type for java's ListPreference
|
|
|
|
pref.type = "string";
|
|
|
|
pref.value = PluginHelper.getPluginPreference();
|
|
|
|
prefs.push(pref);
|
|
|
|
continue;
|
2011-12-21 13:08:01 -08:00
|
|
|
} else if (prefName == MasterPassword.pref) {
|
|
|
|
// Master password is not a "real" pref
|
|
|
|
pref.type = "bool";
|
|
|
|
pref.value = MasterPassword.enabled;
|
|
|
|
prefs.push(pref);
|
|
|
|
continue;
|
2011-12-13 14:28:45 -08:00
|
|
|
}
|
|
|
|
|
2011-10-25 14:31:55 -07:00
|
|
|
try {
|
|
|
|
switch (Services.prefs.getPrefType(prefName)) {
|
|
|
|
case Ci.nsIPrefBranch.PREF_BOOL:
|
|
|
|
pref.type = "bool";
|
|
|
|
pref.value = Services.prefs.getBoolPref(prefName);
|
|
|
|
break;
|
|
|
|
case Ci.nsIPrefBranch.PREF_INT:
|
|
|
|
pref.type = "int";
|
|
|
|
pref.value = Services.prefs.getIntPref(prefName);
|
|
|
|
break;
|
|
|
|
case Ci.nsIPrefBranch.PREF_STRING:
|
|
|
|
default:
|
|
|
|
pref.type = "string";
|
2012-01-23 13:40:07 -08:00
|
|
|
pref.value = Services.prefs.getComplexValue(prefName, Ci.nsIPrefLocalizedString).data;
|
2011-10-25 14:31:55 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// preference does not exist; do not send it
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// some preferences use integers or strings instead of booleans for
|
|
|
|
// indicating enabled/disabled. since the java ui uses the type to
|
|
|
|
// determine which ui elements to show, we need to normalize these
|
|
|
|
// preferences to be actual booleans.
|
|
|
|
switch (prefName) {
|
|
|
|
case "network.cookie.cookieBehavior":
|
2011-10-14 12:48:02 -07:00
|
|
|
pref.type = "bool";
|
2011-10-25 14:31:55 -07:00
|
|
|
pref.value = pref.value == 0;
|
2011-10-14 12:48:02 -07:00
|
|
|
break;
|
2011-12-15 10:43:56 -08:00
|
|
|
case "font.size.inflation.minTwips":
|
|
|
|
pref.type = "string";
|
|
|
|
pref.value = pref.value.toString();
|
|
|
|
break;
|
2011-10-14 12:48:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
prefs.push(pref);
|
|
|
|
}
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Preferences:Data",
|
|
|
|
preferences: prefs
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (e) {}
|
|
|
|
},
|
|
|
|
|
|
|
|
setPreferences: function setPreferences(aPref) {
|
|
|
|
let json = JSON.parse(aPref);
|
|
|
|
|
2011-12-13 14:28:45 -08:00
|
|
|
// The plugin pref is actually two separate prefs, so
|
|
|
|
// we need to handle it differently
|
|
|
|
if (json.name == "plugin.enable") {
|
|
|
|
PluginHelper.setPluginPreference(json.value);
|
|
|
|
return;
|
2011-12-21 13:08:01 -08:00
|
|
|
} else if(json.name == MasterPassword.pref) {
|
|
|
|
if (MasterPassword.enabled)
|
|
|
|
MasterPassword.removePassword(json.value);
|
|
|
|
else
|
|
|
|
MasterPassword.setPassword(json.value);
|
2011-12-13 14:28:45 -08:00
|
|
|
}
|
|
|
|
|
2011-10-25 14:31:55 -07:00
|
|
|
// when sending to java, we normalized special preferences that use
|
|
|
|
// integers and strings to represent booleans. here, we convert them back
|
|
|
|
// to their actual types so we can store them.
|
|
|
|
switch (json.name) {
|
|
|
|
case "network.cookie.cookieBehavior":
|
|
|
|
json.type = "int";
|
|
|
|
json.value = (json.value ? 0 : 2);
|
|
|
|
break;
|
2011-12-15 10:43:56 -08:00
|
|
|
case "font.size.inflation.minTwips":
|
|
|
|
json.type = "int";
|
|
|
|
json.value = parseInt(json.value);
|
|
|
|
break;
|
2011-10-25 14:31:55 -07:00
|
|
|
}
|
|
|
|
|
2011-10-14 12:48:02 -07:00
|
|
|
if (json.type == "bool")
|
|
|
|
Services.prefs.setBoolPref(json.name, json.value);
|
|
|
|
else if (json.type == "int")
|
|
|
|
Services.prefs.setIntPref(json.name, json.value);
|
2011-10-25 14:31:55 -07:00
|
|
|
else {
|
|
|
|
let pref = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString);
|
|
|
|
pref.data = json.value;
|
|
|
|
Services.prefs.setComplexValue(json.name, Ci.nsISupportsString, pref);
|
|
|
|
}
|
2011-10-14 12:48:02 -07:00
|
|
|
},
|
|
|
|
|
2011-12-07 17:52:07 -08:00
|
|
|
getSearchEngines: function() {
|
2012-01-30 18:14:53 -08:00
|
|
|
let engineData = Services.search.getVisibleEngines({});
|
2011-12-07 17:52:07 -08:00
|
|
|
let searchEngines = engineData.map(function (engine) {
|
|
|
|
return {
|
|
|
|
name: engine.name,
|
|
|
|
iconURI: engine.iconURI.spec
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "SearchEngines:Data",
|
|
|
|
searchEngines: searchEngines
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-02-01 16:25:08 -08:00
|
|
|
getSearchOrURI: function getSearchOrURI(aParams) {
|
2011-12-07 17:52:07 -08:00
|
|
|
let uri;
|
2011-12-19 10:44:52 -08:00
|
|
|
if (aParams.engine) {
|
2012-02-01 16:25:08 -08:00
|
|
|
let engine = Services.search.getEngineByName(aParams.engine);
|
2011-12-13 08:31:35 -08:00
|
|
|
if (engine)
|
2011-12-19 10:44:52 -08:00
|
|
|
uri = engine.getSubmission(aParams.url).uri;
|
2011-12-13 08:31:35 -08:00
|
|
|
}
|
2011-12-19 10:44:52 -08:00
|
|
|
return uri ? uri.spec : aParams.url;
|
2011-12-07 17:52:07 -08:00
|
|
|
},
|
|
|
|
|
2011-10-26 11:30:48 -07:00
|
|
|
scrollToFocusedInput: function(aBrowser) {
|
|
|
|
let doc = aBrowser.contentDocument;
|
|
|
|
if (!doc)
|
|
|
|
return;
|
|
|
|
let focused = doc.activeElement;
|
2011-12-03 19:59:27 -08:00
|
|
|
if ((focused instanceof HTMLInputElement && focused.mozIsTextField(false)) || (focused instanceof HTMLTextAreaElement)) {
|
2011-12-29 19:30:47 -08:00
|
|
|
let tab = BrowserApp.getTabForBrowser(aBrowser);
|
|
|
|
let win = aBrowser.contentWindow;
|
|
|
|
|
|
|
|
// tell gecko to scroll the field into view. this will scroll any nested scrollable elements
|
|
|
|
// as well as the browser's content window, and modify the scrollX and scrollY on the content window.
|
2012-01-26 10:16:45 -08:00
|
|
|
focused.scrollIntoView(false);
|
2011-12-29 19:30:47 -08:00
|
|
|
|
|
|
|
// update userScrollPos so that we don't send a duplicate viewport update by triggering
|
|
|
|
// our scroll listener
|
|
|
|
tab.userScrollPos.x = win.scrollX;
|
|
|
|
tab.userScrollPos.y = win.scrollY;
|
|
|
|
|
|
|
|
// note that:
|
|
|
|
// 1. because of the way we do zooming using a CSS transform, gecko does not take into
|
|
|
|
// account the effect of the zoom on the viewport size.
|
|
|
|
// 2. if the input element is near the bottom/right of the page (less than one viewport
|
|
|
|
// height/width away from the bottom/right), the scrollIntoView call will make gecko scroll to the
|
|
|
|
// bottom/right of the page in an attempt to align the input field with the top of the viewport.
|
|
|
|
// however, since gecko doesn't know about the zoom, what it thinks is the "bottom/right of
|
|
|
|
// the page" isn't actually the bottom/right of the page at the current zoom level, and we
|
|
|
|
// need to adjust this further.
|
|
|
|
// 3. we can't actually adjust this by changing the window scroll position, as gecko already thinks
|
|
|
|
// we're at the bottom/right, so instead we do it by changing the viewportExcess on the tab and
|
|
|
|
// moving the browser element.
|
|
|
|
|
|
|
|
let visibleContentWidth = tab._viewport.width / tab._viewport.zoom;
|
|
|
|
let visibleContentHeight = tab._viewport.height / tab._viewport.zoom;
|
|
|
|
// get the rect that the focused element occupies relative to what gecko thinks the viewport is,
|
|
|
|
// and adjust it by viewportExcess to so that it is relative to what the user sees as the viewport.
|
|
|
|
let focusedRect = focused.getBoundingClientRect();
|
|
|
|
focusedRect = {
|
|
|
|
left: focusedRect.left - tab.viewportExcess.x,
|
|
|
|
right: focusedRect.right - tab.viewportExcess.x,
|
|
|
|
top: focusedRect.top - tab.viewportExcess.y,
|
|
|
|
bottom: focusedRect.bottom - tab.viewportExcess.y
|
|
|
|
};
|
|
|
|
let transformChanged = false;
|
|
|
|
if (focusedRect.right >= visibleContentWidth && focusedRect.left > 0) {
|
|
|
|
// the element is too far off the right side, so we need to scroll to the right more
|
|
|
|
tab.viewportExcess.x += Math.min(focusedRect.left, focusedRect.right - visibleContentWidth);
|
|
|
|
transformChanged = true;
|
|
|
|
} else if (focusedRect.left < 0) {
|
|
|
|
// the element is too far off the left side, so we need to scroll to the left more
|
|
|
|
tab.viewportExcess.x += focusedRect.left;
|
|
|
|
transformChanged = true;
|
|
|
|
}
|
|
|
|
if (focusedRect.bottom >= visibleContentHeight && focusedRect.top > 0) {
|
|
|
|
// the element is too far down, so we need to scroll down more
|
|
|
|
tab.viewportExcess.y += Math.min(focusedRect.top, focusedRect.bottom - visibleContentHeight);
|
|
|
|
transformChanged = true;
|
|
|
|
} else if (focusedRect.top < 0) {
|
|
|
|
// the element is too far up, so we need to scroll up more
|
|
|
|
tab.viewportExcess.y += focusedRect.top;
|
|
|
|
transformChanged = true;
|
|
|
|
}
|
|
|
|
if (transformChanged)
|
|
|
|
tab.updateTransform();
|
|
|
|
// finally, let java know where we ended up
|
|
|
|
tab.sendViewportUpdate();
|
2011-12-03 19:59:27 -08:00
|
|
|
}
|
2011-10-26 11:30:48 -07:00
|
|
|
},
|
|
|
|
|
2011-11-18 18:07:14 -08:00
|
|
|
getDrawMetadata: function getDrawMetadata() {
|
2012-01-23 20:10:24 -08:00
|
|
|
let viewport = this.selectedTab.viewport;
|
|
|
|
|
|
|
|
// Sample the background color of the page and pass it along. (This is used to draw the
|
|
|
|
// checkerboard.)
|
|
|
|
try {
|
|
|
|
let browser = this.selectedBrowser;
|
|
|
|
if (browser) {
|
|
|
|
let { contentDocument, contentWindow } = browser;
|
|
|
|
let computedStyle = contentWindow.getComputedStyle(contentDocument.body);
|
|
|
|
viewport.backgroundColor = computedStyle.backgroundColor;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Ignore. Catching and ignoring exceptions here ensures that Talos succeeds.
|
|
|
|
}
|
|
|
|
|
|
|
|
return JSON.stringify(viewport);
|
2011-11-18 18:07:14 -08:00
|
|
|
},
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
let browser = this.selectedBrowser;
|
|
|
|
if (!browser)
|
|
|
|
return;
|
|
|
|
|
2011-10-31 08:02:34 -07:00
|
|
|
if (aTopic == "Session:Back") {
|
2011-10-13 13:06:41 -07:00
|
|
|
browser.goBack();
|
2011-10-31 08:02:34 -07:00
|
|
|
} else if (aTopic == "Session:Forward") {
|
2011-10-31 08:02:34 -07:00
|
|
|
browser.goForward();
|
2011-10-31 08:02:34 -07:00
|
|
|
} else if (aTopic == "Session:Reload") {
|
2011-10-13 13:06:41 -07:00
|
|
|
browser.reload();
|
2011-11-16 16:23:22 -08:00
|
|
|
} else if (aTopic == "Session:Stop") {
|
|
|
|
browser.stop();
|
2011-12-16 11:32:32 -08:00
|
|
|
} else if (aTopic == "Tab:Add" || aTopic == "Tab:Load") {
|
2011-12-19 10:44:52 -08:00
|
|
|
let data = JSON.parse(aData);
|
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
// Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
|
|
|
|
// inheriting the currently loaded document's principal.
|
2012-02-22 18:26:04 -08:00
|
|
|
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
|
|
|
if (data.userEntered)
|
|
|
|
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
|
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
let params = {
|
|
|
|
selected: true,
|
2011-12-19 10:44:52 -08:00
|
|
|
parentId: ("parentId" in data) ? data.parentId : -1,
|
2012-02-22 18:26:04 -08:00
|
|
|
flags: flags
|
2011-12-16 11:32:32 -08:00
|
|
|
};
|
|
|
|
|
2012-02-01 16:25:08 -08:00
|
|
|
let url = this.getSearchOrURI(data);
|
2012-01-19 11:44:16 -08:00
|
|
|
|
|
|
|
// Don't show progress throbber for about:home
|
|
|
|
if (url == "about:home")
|
|
|
|
params.showProgress = false;
|
|
|
|
|
2011-12-16 11:32:32 -08:00
|
|
|
if (aTopic == "Tab:Add")
|
|
|
|
this.addTab(url, params);
|
|
|
|
else
|
|
|
|
this.loadURI(url, browser, params);
|
2012-01-24 09:15:52 -08:00
|
|
|
} else if (aTopic == "Tab:Selected") {
|
|
|
|
this._handleTabSelected(this.getTabForId(parseInt(aData)));
|
2012-01-24 09:15:41 -08:00
|
|
|
} else if (aTopic == "Tab:Closed") {
|
|
|
|
this._handleTabClosed(this.getTabForId(parseInt(aData)));
|
2011-11-16 14:33:43 -08:00
|
|
|
} else if (aTopic == "Browser:Quit") {
|
|
|
|
this.quit();
|
2011-10-27 21:55:15 -07:00
|
|
|
} else if (aTopic == "SaveAs:PDF") {
|
2011-10-25 08:51:23 -07:00
|
|
|
this.saveAsPDF(browser);
|
2011-10-26 11:32:14 -07:00
|
|
|
} else if (aTopic == "Preferences:Get") {
|
2011-10-14 12:48:02 -07:00
|
|
|
this.getPreferences(aData);
|
2011-10-26 11:32:14 -07:00
|
|
|
} else if (aTopic == "Preferences:Set") {
|
2011-10-14 12:48:02 -07:00
|
|
|
this.setPreferences(aData);
|
2011-10-26 11:30:48 -07:00
|
|
|
} else if (aTopic == "ScrollTo:FocusedInput") {
|
|
|
|
this.scrollToFocusedInput(browser);
|
2011-10-27 21:55:15 -07:00
|
|
|
} else if (aTopic == "Sanitize:ClearAll") {
|
|
|
|
Sanitizer.sanitize();
|
2011-11-08 10:36:04 -08:00
|
|
|
} else if (aTopic == "FullScreen:Exit") {
|
|
|
|
browser.contentDocument.mozCancelFullScreen();
|
2011-11-18 18:07:14 -08:00
|
|
|
} else if (aTopic == "Viewport:Change") {
|
2011-11-23 11:07:29 -08:00
|
|
|
this.selectedTab.viewport = JSON.parse(aData);
|
2011-11-29 09:35:26 -08:00
|
|
|
ViewportHandler.onResize();
|
2011-12-07 17:52:07 -08:00
|
|
|
} else if (aTopic == "SearchEngines:Get") {
|
|
|
|
this.getSearchEngines();
|
2011-12-16 15:11:09 -08:00
|
|
|
} else if (aTopic == "Passwords:Init") {
|
|
|
|
var storage = Components.classes["@mozilla.org/login-manager/storage/mozStorage;1"].
|
|
|
|
getService(Components.interfaces.nsILoginManagerStorage);
|
|
|
|
storage.init();
|
|
|
|
|
|
|
|
sendMessageToJava({gecko: { type: "Passwords:Init:Return" }});
|
2012-02-27 10:10:14 -08:00
|
|
|
Services.obs.removeObserver(this, "Passwords:Init", false);
|
|
|
|
} else if (aTopic == "FormHistory:Init") {
|
|
|
|
var fh = Components.classes["@mozilla.org/satchel/form-history;1"].
|
|
|
|
getService(Components.interfaces.nsIFormHistory2);
|
|
|
|
var db = fh.DBConnection;
|
|
|
|
sendMessageToJava({gecko: { type: "FormHistory:Init:Return" }});
|
|
|
|
Services.obs.removeObserver(this, "FormHistory:Init", false);
|
2012-02-14 09:50:55 -08:00
|
|
|
} else if (aTopic == "sessionstore-state-purge-complete") {
|
|
|
|
sendMessageToJava({ gecko: { type: "Session:StatePurged" }});
|
2011-10-26 11:32:14 -07:00
|
|
|
}
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
get defaultBrowserWidth() {
|
|
|
|
delete this.defaultBrowserWidth;
|
|
|
|
let width = Services.prefs.getIntPref("browser.viewport.desktopWidth");
|
|
|
|
return this.defaultBrowserWidth = width;
|
2012-02-17 10:52:26 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
// nsIAndroidBrowserApp
|
|
|
|
getWindowForTab: function(tabId) {
|
|
|
|
let tab = this.getTabForId(tabId);
|
|
|
|
if (!tab.browser)
|
|
|
|
return null;
|
|
|
|
return tab.browser.contentWindow;
|
2012-01-03 22:55:04 -08:00
|
|
|
}
|
2012-02-17 10:52:26 -08:00
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
};
|
2008-11-21 21:12:25 -08:00
|
|
|
|
2011-10-20 22:50:04 -07:00
|
|
|
var NativeWindow = {
|
|
|
|
init: function() {
|
|
|
|
Services.obs.addObserver(this, "Menu:Clicked", false);
|
2011-10-26 13:21:42 -07:00
|
|
|
Services.obs.addObserver(this, "Doorhanger:Reply", false);
|
2011-11-03 15:09:37 -07:00
|
|
|
this.contextmenus.init();
|
2011-10-20 22:50:04 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function() {
|
|
|
|
Services.obs.removeObserver(this, "Menu:Clicked");
|
2011-10-26 13:21:42 -07:00
|
|
|
Services.obs.removeObserver(this, "Doorhanger:Reply");
|
2011-11-03 15:09:37 -07:00
|
|
|
this.contextmenus.uninit();
|
2011-10-20 22:50:04 -07:00
|
|
|
},
|
|
|
|
|
2011-10-25 19:51:44 -07:00
|
|
|
toast: {
|
|
|
|
show: function(aMessage, aDuration) {
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Toast:Show",
|
|
|
|
message: aMessage,
|
|
|
|
duration: aDuration
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-10-20 22:50:04 -07:00
|
|
|
menu: {
|
|
|
|
_callbacks: [],
|
|
|
|
_menuId: 0,
|
|
|
|
add: function(aName, aIcon, aCallback) {
|
2011-10-25 19:51:44 -07:00
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Menu:Add",
|
|
|
|
name: aName,
|
|
|
|
icon: aIcon,
|
|
|
|
id: this._menuId
|
|
|
|
}
|
|
|
|
});
|
2011-10-20 22:50:04 -07:00
|
|
|
this._callbacks[this._menuId] = aCallback;
|
|
|
|
this._menuId++;
|
|
|
|
return this._menuId - 1;
|
|
|
|
},
|
|
|
|
|
|
|
|
remove: function(aId) {
|
|
|
|
sendMessageToJava({ gecko: {type: "Menu:Remove", id: aId }});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-10-26 13:21:42 -07:00
|
|
|
doorhanger: {
|
2011-11-01 13:02:56 -07:00
|
|
|
_callbacks: {},
|
2011-10-26 13:21:42 -07:00
|
|
|
_callbacksId: 0,
|
|
|
|
_promptId: 0,
|
2011-11-11 13:43:38 -08:00
|
|
|
|
2011-11-11 13:44:09 -08:00
|
|
|
/**
|
|
|
|
* @param aOptions
|
|
|
|
* An options JavaScript object holding additional properties for the
|
|
|
|
* notification. The following properties are currently supported:
|
|
|
|
* persistence: An integer. The notification will not automatically
|
2011-11-15 12:30:54 -08:00
|
|
|
* dismiss for this many page loads. If persistence is set
|
|
|
|
* to -1, the doorhanger will never automatically dismiss.
|
2011-11-11 13:44:09 -08:00
|
|
|
* timeout: A time in milliseconds. The notification will not
|
|
|
|
* automatically dismiss before this time.
|
|
|
|
*/
|
|
|
|
show: function(aMessage, aValue, aButtons, aTabID, aOptions) {
|
2011-10-26 13:21:42 -07:00
|
|
|
aButtons.forEach((function(aButton) {
|
|
|
|
this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
|
|
|
|
aButton.callback = this._callbacksId;
|
|
|
|
this._callbacksId++;
|
|
|
|
}).bind(this));
|
|
|
|
|
|
|
|
this._promptId++;
|
|
|
|
let json = {
|
|
|
|
gecko: {
|
|
|
|
type: "Doorhanger:Add",
|
|
|
|
message: aMessage,
|
2011-11-11 13:43:38 -08:00
|
|
|
value: aValue,
|
2011-10-26 13:21:42 -07:00
|
|
|
buttons: aButtons,
|
2011-11-11 13:43:38 -08:00
|
|
|
// use the current tab if none is provided
|
2011-11-11 13:44:09 -08:00
|
|
|
tabID: aTabID || BrowserApp.selectedTab.id,
|
|
|
|
options: aOptions || {}
|
2011-10-26 13:21:42 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(json);
|
2011-11-15 14:45:54 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
hide: function(aValue, aTabID) {
|
|
|
|
sendMessageToJava({
|
|
|
|
type: "Doorhanger:Remove",
|
|
|
|
value: aValue,
|
|
|
|
tabID: aTabID
|
|
|
|
});
|
2011-10-26 13:21:42 -07:00
|
|
|
}
|
|
|
|
},
|
2011-11-01 13:02:56 -07:00
|
|
|
|
2011-10-20 22:50:04 -07:00
|
|
|
observe: function(aSubject, aTopic, aData) {
|
2011-10-26 13:21:42 -07:00
|
|
|
if (aTopic == "Menu:Clicked") {
|
|
|
|
if (this.menu._callbacks[aData])
|
|
|
|
this.menu._callbacks[aData]();
|
|
|
|
} else if (aTopic == "Doorhanger:Reply") {
|
2011-11-03 23:54:47 -07:00
|
|
|
let reply_id = aData;
|
|
|
|
if (this.doorhanger._callbacks[reply_id]) {
|
|
|
|
let prompt = this.doorhanger._callbacks[reply_id].prompt;
|
|
|
|
this.doorhanger._callbacks[reply_id].cb();
|
|
|
|
for (let id in this.doorhanger._callbacks) {
|
|
|
|
if (this.doorhanger._callbacks[id].prompt == prompt) {
|
|
|
|
delete this.doorhanger._callbacks[id];
|
2011-11-01 13:02:56 -07:00
|
|
|
}
|
2011-10-26 13:21:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-11-03 15:09:37 -07:00
|
|
|
},
|
|
|
|
contextmenus: {
|
|
|
|
items: {}, // a list of context menu items that we may show
|
|
|
|
_contextId: 0, // id to assign to new context menu items if they are added
|
|
|
|
|
|
|
|
init: function() {
|
2011-12-19 08:44:48 -08:00
|
|
|
this.imageContext = this.SelectorContext("img");
|
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
Services.obs.addObserver(this, "Gesture:LongPress", false);
|
|
|
|
|
|
|
|
// TODO: These should eventually move into more appropriate classes
|
|
|
|
this.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"),
|
2012-01-06 11:44:33 -08:00
|
|
|
this.linkOpenableContext,
|
2011-11-03 15:09:37 -07:00
|
|
|
function(aTarget) {
|
|
|
|
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
|
2011-12-19 10:44:52 -08:00
|
|
|
BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
|
2011-11-27 06:49:00 -08:00
|
|
|
|
|
|
|
let newtabStrings = Strings.browser.GetStringFromName("newtabpopup.opened");
|
|
|
|
let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
|
|
|
|
NativeWindow.toast.show(label, "short");
|
2011-11-03 15:09:37 -07:00
|
|
|
});
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2012-02-16 11:07:12 -08:00
|
|
|
this.add(Strings.browser.GetStringFromName("contextmenu.shareLink"),
|
|
|
|
this.linkShareableContext,
|
|
|
|
function(aTarget) {
|
|
|
|
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
|
|
|
|
let title = aTarget.textContent || aTarget.title;
|
|
|
|
let sharing = Cc["@mozilla.org/uriloader/external-sharing-app-service;1"].getService(Ci.nsIExternalSharingAppService);
|
|
|
|
sharing.shareWithDefault(url, "text/plain", title);
|
|
|
|
});
|
|
|
|
|
2012-02-21 21:11:50 -08:00
|
|
|
this.add(Strings.browser.GetStringFromName("contextmenu.bookmarkLink"),
|
|
|
|
this.linkBookmarkableContext,
|
|
|
|
function(aTarget) {
|
|
|
|
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
|
|
|
|
let title = aTarget.textContent || aTarget.title || url;
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Bookmark:Insert",
|
|
|
|
url: url,
|
|
|
|
title: title
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2011-11-08 10:36:04 -08:00
|
|
|
this.add(Strings.browser.GetStringFromName("contextmenu.fullScreen"),
|
2011-11-11 15:51:12 -08:00
|
|
|
this.SelectorContext("video:not(:-moz-full-screen)"),
|
2011-11-08 10:36:04 -08:00
|
|
|
function(aTarget) {
|
|
|
|
aTarget.mozRequestFullScreen();
|
|
|
|
});
|
2011-12-19 08:44:48 -08:00
|
|
|
|
|
|
|
this.add(Strings.browser.GetStringFromName("contextmenu.saveImage"),
|
|
|
|
this.imageContext,
|
|
|
|
function(aTarget) {
|
|
|
|
let imageCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
|
|
|
|
let props = imageCache.findEntryProperties(aTarget.currentURI, aTarget.ownerDocument.characterSet);
|
|
|
|
var contentDisposition = "";
|
|
|
|
var type = "";
|
|
|
|
try {
|
|
|
|
String(props.get("content-disposition", Ci.nsISupportsCString));
|
|
|
|
String(props.get("type", Ci.nsISupportsCString));
|
|
|
|
} catch(ex) { }
|
|
|
|
var browser = BrowserApp.getBrowserForDocument(aTarget.ownerDocument);
|
|
|
|
ContentAreaUtils.internalSave(aTarget.currentURI.spec, null, null, contentDisposition, type, false, "SaveImageTitle", null, browser.documentURI, true, null);
|
|
|
|
});
|
2011-11-03 15:09:37 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function() {
|
|
|
|
Services.obs.removeObserver(this, "Gesture:LongPress");
|
|
|
|
},
|
|
|
|
|
|
|
|
add: function(aName, aSelector, aCallback) {
|
|
|
|
if (!aName)
|
|
|
|
throw "Menu items must have a name";
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
let item = {
|
|
|
|
name: aName,
|
|
|
|
context: aSelector,
|
|
|
|
callback: aCallback,
|
|
|
|
matches: function(aElt) {
|
|
|
|
return this.context.matches(aElt);
|
|
|
|
},
|
|
|
|
getValue: function() {
|
|
|
|
return {
|
|
|
|
label: this.name,
|
|
|
|
id: this.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
item.id = this._contextId++;
|
|
|
|
this.items[item.id] = item;
|
|
|
|
return item.id;
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
remove: function(aId) {
|
2012-01-31 21:25:01 -08:00
|
|
|
delete this.items[aId];
|
2011-11-03 15:09:37 -07:00
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
SelectorContext: function(aSelector) {
|
|
|
|
return {
|
|
|
|
matches: function(aElt) {
|
|
|
|
if (aElt.mozMatchesSelector)
|
|
|
|
return aElt.mozMatchesSelector(aSelector);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2012-01-06 11:44:33 -08:00
|
|
|
linkOpenableContext: {
|
|
|
|
matches: function linkOpenableContextMatches(aElement) {
|
2012-02-21 21:11:50 -08:00
|
|
|
let uri = NativeWindow.contextmenus._getLink(aElement);
|
|
|
|
if (uri) {
|
2012-01-06 11:44:33 -08:00
|
|
|
let scheme = uri.scheme;
|
|
|
|
let dontOpen = /^(mailto|javascript|news|snews)$/;
|
|
|
|
return (scheme && !dontOpen.test(scheme));
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-16 11:07:12 -08:00
|
|
|
linkShareableContext: {
|
|
|
|
matches: function linkShareableContextMatches(aElement) {
|
2012-02-21 21:11:50 -08:00
|
|
|
let uri = NativeWindow.contextmenus._getLink(aElement);
|
|
|
|
if (uri) {
|
2012-02-16 11:07:12 -08:00
|
|
|
let scheme = uri.scheme;
|
|
|
|
let dontShare = /^(chrome|about|file|javascript|resource)$/;
|
|
|
|
return (scheme && !dontShare.test(scheme));
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-21 21:11:50 -08:00
|
|
|
linkBookmarkableContext: {
|
|
|
|
matches: function linkBookmarkableContextMatches(aElement) {
|
|
|
|
let uri = NativeWindow.contextmenus._getLink(aElement);
|
|
|
|
if (uri) {
|
|
|
|
let scheme = uri.scheme;
|
|
|
|
let dontBookmark = /^(mailto)$/;
|
|
|
|
return (scheme && !dontBookmark.test(scheme));
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-01-11 16:36:11 -08:00
|
|
|
textContext: {
|
|
|
|
matches: function textContext(aElement) {
|
|
|
|
return ((aElement instanceof Ci.nsIDOMHTMLInputElement && aElement.mozIsTextField(false))
|
|
|
|
|| aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
_sendToContent: function(aX, aY) {
|
|
|
|
// initially we look for nearby clickable elements. If we don't find one we fall back to using whatever this click was on
|
|
|
|
let rootElement = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow, aX, aY);
|
|
|
|
if (!rootElement)
|
|
|
|
rootElement = ElementTouchHelper.anyElementFromPoint(BrowserApp.selectedBrowser.contentWindow, aX, aY)
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
this.menuitems = null;
|
|
|
|
let element = rootElement;
|
|
|
|
if (!element)
|
|
|
|
return;
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
while (element) {
|
|
|
|
for each (let item in this.items) {
|
|
|
|
// since we'll have to spin through this for each element, check that
|
|
|
|
// it is not already in the list
|
|
|
|
if ((!this.menuitems || !this.menuitems[item.id]) && item.matches(element)) {
|
|
|
|
if (!this.menuitems)
|
|
|
|
this.menuitems = {};
|
|
|
|
this.menuitems[item.id] = item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-06 11:44:33 -08:00
|
|
|
if (this.linkOpenableContext.matches(element) || this.textContext.matches(element))
|
2011-11-03 15:09:37 -07:00
|
|
|
break;
|
|
|
|
element = element.parentNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only send the contextmenu event to content if we are planning to show a context menu (i.e. not on every long tap)
|
|
|
|
if (this.menuitems) {
|
|
|
|
BrowserEventHandler.blockClick = true;
|
|
|
|
let event = rootElement.ownerDocument.createEvent("MouseEvent");
|
|
|
|
event.initMouseEvent("contextmenu", true, true, content,
|
|
|
|
0, aX, aY, aX, aY, false, false, false, false,
|
|
|
|
0, null);
|
|
|
|
rootElement.ownerDocument.defaultView.addEventListener("contextmenu", this, false);
|
|
|
|
rootElement.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
_show: function(aEvent) {
|
2012-01-03 10:57:36 -08:00
|
|
|
if (aEvent.defaultPrevented)
|
2011-11-03 15:09:37 -07:00
|
|
|
return;
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
let popupNode = aEvent.originalTarget;
|
|
|
|
let title = "";
|
|
|
|
if ((popupNode instanceof Ci.nsIDOMHTMLAnchorElement && popupNode.href) ||
|
|
|
|
(popupNode instanceof Ci.nsIDOMHTMLAreaElement && popupNode.href)) {
|
|
|
|
title = this._getLinkURL(popupNode);
|
|
|
|
} else if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
|
|
|
|
title = popupNode.currentURI.spec;
|
|
|
|
} else if (popupNode instanceof Ci.nsIDOMHTMLMediaElement) {
|
2011-11-07 09:00:52 -08:00
|
|
|
title = (popupNode.currentSrc || popupNode.src);
|
2011-11-03 15:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// convert this.menuitems object to an array for sending to native code
|
|
|
|
let itemArray = [];
|
|
|
|
for each (let item in this.menuitems) {
|
|
|
|
itemArray.push(item.getValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
let msg = {
|
|
|
|
gecko: {
|
|
|
|
type: "Prompt:Show",
|
|
|
|
title: title,
|
|
|
|
listitems: itemArray
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let data = JSON.parse(sendMessageToJava(msg));
|
|
|
|
let selectedId = itemArray[data.button].id;
|
|
|
|
let selectedItem = this.menuitems[selectedId];
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
if (selectedItem && selectedItem.callback) {
|
|
|
|
while (popupNode) {
|
|
|
|
if (selectedItem.matches(popupNode)) {
|
|
|
|
selectedItem.callback.call(selectedItem, popupNode);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
popupNode = popupNode.parentNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.menuitems = null;
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
handleEvent: function(aEvent) {
|
|
|
|
aEvent.target.ownerDocument.defaultView.removeEventListener("contextmenu", this, false);
|
|
|
|
this._show(aEvent);
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
observe: function(aSubject, aTopic, aData) {
|
2011-11-30 13:10:25 -08:00
|
|
|
BrowserEventHandler._cancelTapHighlight();
|
2011-11-03 15:09:37 -07:00
|
|
|
let data = JSON.parse(aData);
|
|
|
|
// content gets first crack at cancelling context menus
|
|
|
|
this._sendToContent(data.x, data.y);
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
// XXX - These are stolen from Util.js, we should remove them if we bring it back
|
|
|
|
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;
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
|
|
|
|
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
|
|
|
},
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2012-02-21 21:11:50 -08:00
|
|
|
_getLink: function(aElement) {
|
|
|
|
if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
|
|
|
|
((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
|
|
|
|
(aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) ||
|
|
|
|
aElement instanceof Ci.nsIDOMHTMLLinkElement ||
|
|
|
|
aElement.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
|
|
|
|
try {
|
|
|
|
let url = NativeWindow.contextmenus._getLinkURL(aElement);
|
|
|
|
return Services.io.newURI(url, null, null);
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
_getLinkURL: function ch_getLinkURL(aLink) {
|
|
|
|
let href = aLink.href;
|
|
|
|
if (href)
|
|
|
|
return href;
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
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";
|
|
|
|
}
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2012-01-06 11:44:33 -08:00
|
|
|
return this.makeURLAbsolute(aLink.baseURI, href);
|
2011-11-03 15:09:37 -07:00
|
|
|
}
|
2011-10-20 22:50:04 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-03-26 08:11:03 -07:00
|
|
|
|
2011-10-12 14:31:04 -07:00
|
|
|
function nsBrowserAccess() {
|
2011-10-10 17:40:17 -07:00
|
|
|
}
|
2010-06-09 16:07:12 -07:00
|
|
|
|
2011-10-10 17:40:17 -07:00
|
|
|
nsBrowserAccess.prototype = {
|
2011-12-16 14:01:03 -08:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
|
|
|
|
|
|
|
|
_getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
|
2011-10-27 17:25:34 -07:00
|
|
|
let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
|
2011-12-16 14:01:03 -08:00
|
|
|
if (isExternal && aURI && aURI.schemeIs("chrome"))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let loadflags = isExternal ?
|
|
|
|
Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
|
|
|
|
Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
|
|
if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
|
|
|
|
switch (aContext) {
|
|
|
|
case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL:
|
|
|
|
aWhere = Services.prefs.getIntPref("browser.link.open_external");
|
|
|
|
break;
|
|
|
|
default: // OPEN_NEW or an illegal value
|
|
|
|
aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
|
|
|
|
}
|
|
|
|
}
|
2011-10-27 17:25:34 -07:00
|
|
|
|
2012-02-13 17:39:17 -08:00
|
|
|
Services.io.offline = false;
|
2011-12-19 10:44:52 -08:00
|
|
|
|
2012-02-13 17:39:17 -08:00
|
|
|
let referrer;
|
|
|
|
if (aOpener) {
|
|
|
|
try {
|
|
|
|
let location = aOpener.location;
|
|
|
|
referrer = Services.io.newURI(location, null, null);
|
|
|
|
} catch(e) { }
|
2011-12-19 10:44:52 -08:00
|
|
|
}
|
|
|
|
|
2012-02-13 17:39:17 -08:00
|
|
|
let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW || aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB);
|
|
|
|
|
2011-12-19 10:44:52 -08:00
|
|
|
if (newTab) {
|
2012-02-13 17:39:17 -08:00
|
|
|
let parentId = -1;
|
|
|
|
if (!isExternal) {
|
|
|
|
let parent = BrowserApp.getTabForBrowser(BrowserApp.getBrowserForWindow(aOpener.top));
|
|
|
|
if (parent)
|
|
|
|
parentId = parent.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// BrowserApp.addTab calls loadURIWithFlags with the appropriate params
|
|
|
|
let tab = BrowserApp.addTab(aURI ? aURI.spec : "about:blank", { flags: loadflags,
|
|
|
|
referrerURI: referrer,
|
|
|
|
external: isExternal,
|
|
|
|
parentId: parentId,
|
|
|
|
selected: true });
|
|
|
|
return tab.browser;
|
2011-10-27 17:25:34 -07:00
|
|
|
}
|
2011-10-12 14:31:04 -07:00
|
|
|
|
2012-02-13 17:39:17 -08:00
|
|
|
// OPEN_CURRENTWINDOW and illegal values
|
|
|
|
let browser = BrowserApp.selectedBrowser;
|
|
|
|
if (aURI && browser)
|
|
|
|
browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
|
2011-12-16 14:01:03 -08:00
|
|
|
|
|
|
|
return browser;
|
|
|
|
},
|
|
|
|
|
|
|
|
openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) {
|
|
|
|
let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
|
2011-12-26 21:58:18 -08:00
|
|
|
return browser ? browser.contentWindow : null;
|
2009-01-08 22:51:13 -08:00
|
|
|
},
|
|
|
|
|
2011-10-10 17:40:17 -07:00
|
|
|
openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
|
2011-12-16 14:01:03 -08:00
|
|
|
let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
|
|
|
|
return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
|
2011-03-02 06:13:25 -08:00
|
|
|
},
|
|
|
|
|
2011-10-10 17:40:17 -07:00
|
|
|
isTabContentWindow: function(aWindow) {
|
2011-10-13 13:06:41 -07:00
|
|
|
return BrowserApp.getBrowserForWindow(aWindow) != null;
|
2011-12-16 14:01:03 -08:00
|
|
|
}
|
2011-10-10 17:40:17 -07:00
|
|
|
};
|
2008-04-18 06:41:49 -07:00
|
|
|
|
2011-10-14 23:22:25 -07:00
|
|
|
|
|
|
|
let gTabIDFactory = 0;
|
|
|
|
|
2011-11-29 06:58:54 -08:00
|
|
|
// track the last known screen size so that new tabs
|
|
|
|
// get created with the right size rather than being 1x1
|
|
|
|
let gScreenWidth = 1;
|
|
|
|
let gScreenHeight = 1;
|
|
|
|
|
2011-11-08 12:49:25 -08:00
|
|
|
function Tab(aURL, aParams) {
|
2011-10-13 13:06:41 -07:00
|
|
|
this.browser = null;
|
2011-11-23 11:07:29 -08:00
|
|
|
this.vbox = null;
|
2011-10-14 23:22:25 -07:00
|
|
|
this.id = 0;
|
2012-01-19 11:44:16 -08:00
|
|
|
this.showProgress = true;
|
2011-11-08 12:49:25 -08:00
|
|
|
this.create(aURL, aParams);
|
2011-11-29 06:58:54 -08:00
|
|
|
this._viewport = { x: 0, y: 0, width: gScreenWidth, height: gScreenHeight, offsetX: 0, offsetY: 0,
|
2011-12-16 14:01:02 -08:00
|
|
|
pageWidth: gScreenWidth, pageHeight: gScreenHeight, zoom: 1.0 };
|
2011-11-23 11:07:47 -08:00
|
|
|
this.viewportExcess = { x: 0, y: 0 };
|
2012-01-06 16:42:46 -08:00
|
|
|
this.documentIdForCurrentViewport = null;
|
2011-12-06 10:50:21 -08:00
|
|
|
this.userScrollPos = { x: 0, y: 0 };
|
2012-01-31 12:49:25 -08:00
|
|
|
this._pluginCount = 0;
|
2011-12-09 14:04:19 -08:00
|
|
|
this._pluginOverlayShowing = false;
|
2011-10-10 17:40:17 -07:00
|
|
|
}
|
2009-11-30 12:19:24 -08:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
Tab.prototype = {
|
2011-11-08 12:49:25 -08:00
|
|
|
create: function(aURL, aParams) {
|
2011-10-13 13:06:41 -07:00
|
|
|
if (this.browser)
|
|
|
|
return;
|
|
|
|
|
2012-01-26 15:07:38 -08:00
|
|
|
aParams = aParams || {};
|
2011-12-18 22:28:44 -08:00
|
|
|
|
2011-11-23 11:07:29 -08:00
|
|
|
this.vbox = document.createElement("vbox");
|
|
|
|
this.vbox.align = "start";
|
|
|
|
BrowserApp.deck.appendChild(this.vbox);
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
this.browser = document.createElement("browser");
|
2012-01-31 09:35:23 -08:00
|
|
|
this.browser.setAttribute("type", "content-targetable");
|
2011-11-29 09:35:26 -08:00
|
|
|
this.setBrowserSize(980, 480);
|
2011-11-23 11:07:47 -08:00
|
|
|
this.browser.style.MozTransformOrigin = "0 0";
|
2011-11-23 11:07:29 -08:00
|
|
|
this.vbox.appendChild(this.browser);
|
2011-11-15 08:58:23 -08:00
|
|
|
|
2011-12-18 22:28:44 -08:00
|
|
|
this.browser.stop();
|
|
|
|
|
2011-11-23 11:07:29 -08:00
|
|
|
// Turn off clipping so we can buffer areas outside of the browser element.
|
2011-11-15 08:58:23 -08:00
|
|
|
let frameLoader = this.browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
|
|
|
|
frameLoader.clipSubdocument = false;
|
|
|
|
|
2011-10-14 23:22:25 -07:00
|
|
|
this.id = ++gTabIDFactory;
|
2011-12-18 22:28:44 -08:00
|
|
|
|
2011-10-14 22:35:15 -07:00
|
|
|
let message = {
|
|
|
|
gecko: {
|
2011-10-24 12:20:27 -07:00
|
|
|
type: "Tab:Added",
|
2011-10-14 23:22:25 -07:00
|
|
|
tabID: this.id,
|
2011-11-08 12:49:25 -08:00
|
|
|
uri: aURL,
|
2011-12-19 10:44:52 -08:00
|
|
|
parentId: ("parentId" in aParams) ? aParams.parentId : -1,
|
|
|
|
external: ("external" in aParams) ? aParams.external : false,
|
2011-12-20 17:41:45 -08:00
|
|
|
selected: ("selected" in aParams) ? aParams.selected : true,
|
2012-01-31 14:13:33 -08:00
|
|
|
title: aParams.title || "",
|
|
|
|
delayLoad: aParams.delayLoad || false
|
2011-10-14 22:35:15 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
2011-11-02 21:27:57 -07:00
|
|
|
|
|
|
|
let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
|
2011-11-03 15:53:34 -07:00
|
|
|
Ci.nsIWebProgress.NOTIFY_LOCATION |
|
|
|
|
Ci.nsIWebProgress.NOTIFY_SECURITY;
|
2011-11-02 21:27:57 -07:00
|
|
|
this.browser.addProgressListener(this, flags);
|
|
|
|
this.browser.sessionHistory.addSHistoryListener(this);
|
2011-12-16 11:32:32 -08:00
|
|
|
|
2011-12-06 10:53:10 -08:00
|
|
|
this.browser.addEventListener("DOMContentLoaded", this, true);
|
|
|
|
this.browser.addEventListener("DOMLinkAdded", this, true);
|
|
|
|
this.browser.addEventListener("DOMTitleChanged", this, true);
|
2011-12-30 11:48:21 -08:00
|
|
|
this.browser.addEventListener("DOMWindowClose", this, true);
|
2011-12-06 10:50:21 -08:00
|
|
|
this.browser.addEventListener("scroll", this, true);
|
2011-12-09 14:04:19 -08:00
|
|
|
this.browser.addEventListener("PluginClickToPlay", this, true);
|
|
|
|
this.browser.addEventListener("pagehide", this, true);
|
2012-01-06 16:42:46 -08:00
|
|
|
this.browser.addEventListener("pageshow", this, true);
|
2011-12-16 11:32:32 -08:00
|
|
|
|
2012-01-06 16:42:46 -08:00
|
|
|
Services.obs.addObserver(this, "document-shown", false);
|
2011-12-16 11:32:32 -08:00
|
|
|
|
2011-12-20 17:41:45 -08:00
|
|
|
if (!aParams.delayLoad) {
|
|
|
|
let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
|
|
let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
|
|
|
|
let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
|
|
|
|
let charset = "charset" in aParams ? aParams.charset : null;
|
2011-12-16 11:32:32 -08:00
|
|
|
|
2012-01-19 11:44:16 -08:00
|
|
|
// This determines whether or not we show the progress throbber in the urlbar
|
|
|
|
this.showProgress = "showProgress" in aParams ? aParams.showProgress : true;
|
|
|
|
|
2011-12-20 17:41:45 -08:00
|
|
|
try {
|
|
|
|
this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData);
|
|
|
|
} catch(e) {
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "Content:LoadError",
|
|
|
|
tabID: this.id,
|
|
|
|
uri: this.browser.currentURI.spec,
|
|
|
|
title: this.browser.contentTitle
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
|
|
|
dump("Handled load error: " + e)
|
|
|
|
}
|
2011-12-16 11:32:32 -08:00
|
|
|
}
|
2011-10-13 13:06:41 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
if (!this.browser)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.browser.removeProgressListener(this);
|
2011-12-06 10:53:10 -08:00
|
|
|
this.browser.removeEventListener("DOMContentLoaded", this, true);
|
|
|
|
this.browser.removeEventListener("DOMLinkAdded", this, true);
|
|
|
|
this.browser.removeEventListener("DOMTitleChanged", this, true);
|
2011-12-30 11:48:21 -08:00
|
|
|
this.browser.removeEventListener("DOMWindowClose", this, true);
|
2011-12-06 10:50:21 -08:00
|
|
|
this.browser.removeEventListener("scroll", this, true);
|
2011-12-09 14:04:19 -08:00
|
|
|
this.browser.removeEventListener("PluginClickToPlay", this, true);
|
|
|
|
this.browser.removeEventListener("pagehide", this, true);
|
2012-01-06 16:42:46 -08:00
|
|
|
this.browser.removeEventListener("pageshow", this, true);
|
2011-12-20 21:13:20 -08:00
|
|
|
|
2012-02-01 16:20:13 -08:00
|
|
|
Services.obs.removeObserver(this, "document-shown");
|
|
|
|
|
2011-12-20 21:13:20 -08:00
|
|
|
// Make sure the previously selected panel remains selected. The selected panel of a deck is
|
|
|
|
// not stable when panels are removed.
|
|
|
|
let selectedPanel = BrowserApp.deck.selectedPanel;
|
2011-11-23 11:07:29 -08:00
|
|
|
BrowserApp.deck.removeChild(this.vbox);
|
2011-12-20 21:13:20 -08:00
|
|
|
BrowserApp.deck.selectedPanel = selectedPanel;
|
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
this.browser = null;
|
2011-11-23 11:07:29 -08:00
|
|
|
this.vbox = null;
|
2012-01-06 16:42:46 -08:00
|
|
|
this.documentIdForCurrentViewport = null;
|
2011-10-13 13:06:41 -07:00
|
|
|
},
|
|
|
|
|
2012-01-31 09:35:23 -08:00
|
|
|
// This should be called to update the browser when the tab gets selected/unselected
|
|
|
|
setActive: function setActive(aActive) {
|
2011-10-13 13:06:41 -07:00
|
|
|
if (!this.browser)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (aActive) {
|
|
|
|
this.browser.setAttribute("type", "content-primary");
|
2011-10-20 15:48:26 -07:00
|
|
|
this.browser.focus();
|
2012-01-31 09:35:23 -08:00
|
|
|
this.browser.docShellIsActive = true;
|
2011-10-13 13:06:41 -07:00
|
|
|
} else {
|
2012-01-31 09:35:23 -08:00
|
|
|
this.browser.setAttribute("type", "content-targetable");
|
|
|
|
this.browser.docShellIsActive = false;
|
2011-10-13 13:06:41 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-11-23 11:07:29 -08:00
|
|
|
set viewport(aViewport) {
|
2011-11-23 11:07:47 -08:00
|
|
|
// Transform coordinates based on zoom
|
|
|
|
aViewport.x /= aViewport.zoom;
|
|
|
|
aViewport.y /= aViewport.zoom;
|
2011-11-23 11:07:29 -08:00
|
|
|
|
|
|
|
// Set scroll position
|
2011-12-06 10:50:21 -08:00
|
|
|
let win = this.browser.contentWindow;
|
|
|
|
win.scrollTo(aViewport.x, aViewport.y);
|
|
|
|
this.userScrollPos.x = win.scrollX;
|
|
|
|
this.userScrollPos.y = win.scrollY;
|
2011-11-23 11:07:29 -08:00
|
|
|
|
2011-11-23 11:07:47 -08:00
|
|
|
// If we've been asked to over-scroll, do it via the transformation
|
|
|
|
// and store it separately to the viewport.
|
2011-12-06 10:50:21 -08:00
|
|
|
let excessX = aViewport.x - win.scrollX;
|
|
|
|
let excessY = aViewport.y - win.scrollY;
|
2011-11-23 11:07:47 -08:00
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
this._viewport.width = gScreenWidth = aViewport.width;
|
|
|
|
this._viewport.height = gScreenHeight = aViewport.height;
|
2011-11-23 11:07:29 -08:00
|
|
|
|
|
|
|
let transformChanged = false;
|
|
|
|
|
2011-11-23 11:07:47 -08:00
|
|
|
if ((aViewport.offsetX != this._viewport.offsetX) ||
|
|
|
|
(excessX != this.viewportExcess.x)) {
|
2011-11-23 11:07:29 -08:00
|
|
|
this._viewport.offsetX = aViewport.offsetX;
|
2011-11-23 11:07:47 -08:00
|
|
|
this.viewportExcess.x = excessX;
|
2011-11-23 11:07:29 -08:00
|
|
|
transformChanged = true;
|
|
|
|
}
|
2011-11-23 11:07:47 -08:00
|
|
|
if ((aViewport.offsetY != this._viewport.offsetY) ||
|
|
|
|
(excessY != this.viewportExcess.y)) {
|
2011-11-23 11:07:29 -08:00
|
|
|
this._viewport.offsetY = aViewport.offsetY;
|
2011-11-23 11:07:47 -08:00
|
|
|
this.viewportExcess.y = excessY;
|
|
|
|
transformChanged = true;
|
|
|
|
}
|
2011-11-23 11:07:51 -08:00
|
|
|
if (Math.abs(aViewport.zoom - this._viewport.zoom) >= 1e-6) {
|
2011-11-23 11:07:47 -08:00
|
|
|
this._viewport.zoom = aViewport.zoom;
|
2011-11-23 11:07:29 -08:00
|
|
|
transformChanged = true;
|
|
|
|
}
|
|
|
|
|
2011-12-29 19:30:47 -08:00
|
|
|
if (transformChanged)
|
|
|
|
this.updateTransform();
|
|
|
|
},
|
2011-11-23 11:07:51 -08:00
|
|
|
|
2011-12-29 19:30:47 -08:00
|
|
|
updateTransform: function() {
|
|
|
|
let hasZoom = (Math.abs(this._viewport.zoom - 1.0) >= 1e-6);
|
|
|
|
let x = this._viewport.offsetX + Math.round(-this.viewportExcess.x * this._viewport.zoom);
|
|
|
|
let y = this._viewport.offsetY + Math.round(-this.viewportExcess.y * this._viewport.zoom);
|
2011-11-23 11:07:51 -08:00
|
|
|
|
2011-12-29 19:30:47 -08:00
|
|
|
let transform =
|
|
|
|
"translate(" + x + "px, " +
|
|
|
|
y + "px)";
|
|
|
|
if (hasZoom)
|
|
|
|
transform += " scale(" + this._viewport.zoom + ")";
|
2011-11-23 11:07:51 -08:00
|
|
|
|
2011-12-29 19:30:47 -08:00
|
|
|
this.browser.style.MozTransform = transform;
|
2011-11-23 11:07:29 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
get viewport() {
|
|
|
|
// Update the viewport to current dimensions
|
2011-12-13 11:12:07 -08:00
|
|
|
this._viewport.x = (this.browser.contentWindow.scrollX +
|
|
|
|
this.viewportExcess.x) || 0;
|
|
|
|
this._viewport.y = (this.browser.contentWindow.scrollY +
|
|
|
|
this.viewportExcess.y) || 0;
|
2011-11-23 11:07:29 -08:00
|
|
|
|
2011-12-08 21:30:26 -08:00
|
|
|
// Transform coordinates based on zoom
|
|
|
|
this._viewport.x = Math.round(this._viewport.x * this._viewport.zoom);
|
|
|
|
this._viewport.y = Math.round(this._viewport.y * this._viewport.zoom);
|
|
|
|
|
2011-11-28 21:40:53 -08:00
|
|
|
let doc = this.browser.contentDocument;
|
2011-12-26 21:57:27 -08:00
|
|
|
if (doc != null) {
|
2011-12-08 21:30:26 -08:00
|
|
|
let pageWidth = this._viewport.width, pageHeight = this._viewport.height;
|
2012-01-26 06:55:29 -08:00
|
|
|
if (doc instanceof SVGDocument) {
|
|
|
|
let rect = doc.rootElement.getBoundingClientRect();
|
|
|
|
// we need to add rect.left and rect.top twice so that the SVG is drawn
|
|
|
|
// centered on the page; if we add it only once then the SVG will be
|
|
|
|
// on the bottom-right of the page and if we don't add it at all then
|
|
|
|
// we end up with a cropped SVG (see bug 712065)
|
|
|
|
pageWidth = Math.ceil(rect.left + rect.width + rect.left);
|
|
|
|
pageHeight = Math.ceil(rect.top + rect.height + rect.top);
|
|
|
|
} else {
|
|
|
|
let body = doc.body || { scrollWidth: pageWidth, scrollHeight: pageHeight };
|
|
|
|
let html = doc.documentElement || { scrollWidth: pageWidth, scrollHeight: pageHeight };
|
|
|
|
pageWidth = Math.max(body.scrollWidth, html.scrollWidth);
|
|
|
|
pageHeight = Math.max(body.scrollHeight, html.scrollHeight);
|
|
|
|
}
|
2011-11-23 11:07:29 -08:00
|
|
|
|
2011-12-08 21:30:26 -08:00
|
|
|
/* Transform the page width and height based on the zoom factor. */
|
2012-01-12 19:47:33 -08:00
|
|
|
pageWidth *= this._viewport.zoom;
|
|
|
|
pageHeight *= this._viewport.zoom;
|
2011-12-26 21:57:27 -08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Avoid sending page sizes of less than screen size before we hit DOMContentLoaded, because
|
|
|
|
* this causes the page size to jump around wildly during page load. After the page is loaded,
|
|
|
|
* send updates regardless of page size; we'll zoom to fit the content as needed.
|
|
|
|
*/
|
|
|
|
if (doc.readyState === 'complete' || (pageWidth >= gScreenWidth && pageHeight >= gScreenHeight)) {
|
|
|
|
this._viewport.pageWidth = pageWidth;
|
|
|
|
this._viewport.pageHeight = pageHeight;
|
|
|
|
}
|
2011-12-08 21:30:26 -08:00
|
|
|
}
|
2011-11-23 11:07:47 -08:00
|
|
|
|
2011-11-23 11:07:29 -08:00
|
|
|
return this._viewport;
|
|
|
|
},
|
|
|
|
|
2012-01-20 21:14:03 -08:00
|
|
|
updateViewport: function(aReset, aZoomLevel) {
|
|
|
|
if (!aZoomLevel)
|
|
|
|
aZoomLevel = this.getDefaultZoomLevel();
|
|
|
|
|
2011-11-29 06:58:43 -08:00
|
|
|
let win = this.browser.contentWindow;
|
2012-01-20 21:14:03 -08:00
|
|
|
let zoom = (aReset ? aZoomLevel : this._viewport.zoom);
|
2011-12-13 11:12:07 -08:00
|
|
|
let xpos = ((aReset && win) ? win.scrollX * zoom : this._viewport.x);
|
|
|
|
let ypos = ((aReset && win) ? win.scrollY * zoom : this._viewport.y);
|
2011-12-07 19:21:03 -08:00
|
|
|
|
2011-11-29 06:58:43 -08:00
|
|
|
this.viewportExcess = { x: 0, y: 0 };
|
|
|
|
this.viewport = { x: xpos, y: ypos,
|
|
|
|
offsetX: 0, offsetY: 0,
|
|
|
|
width: this._viewport.width, height: this._viewport.height,
|
2011-12-16 14:01:02 -08:00
|
|
|
pageWidth: gScreenWidth, pageHeight: gScreenHeight,
|
2011-11-29 06:58:43 -08:00
|
|
|
zoom: zoom };
|
2011-12-01 06:36:41 -08:00
|
|
|
this.sendViewportUpdate();
|
|
|
|
},
|
|
|
|
|
|
|
|
sendViewportUpdate: function() {
|
2011-12-12 10:53:39 -08:00
|
|
|
if (BrowserApp.selectedTab != this)
|
|
|
|
return;
|
2011-11-29 06:58:43 -08:00
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
2012-01-03 22:55:04 -08:00
|
|
|
type: "Viewport:UpdateAndDraw"
|
2011-11-29 06:58:43 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2011-12-06 10:50:21 -08:00
|
|
|
handleEvent: function(aEvent) {
|
|
|
|
switch (aEvent.type) {
|
2011-12-06 10:53:10 -08:00
|
|
|
case "DOMContentLoaded": {
|
|
|
|
let target = aEvent.originalTarget;
|
|
|
|
|
|
|
|
// ignore on frames
|
|
|
|
if (target.defaultView != this.browser.contentWindow)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "DOMContentLoaded",
|
|
|
|
tabID: this.id,
|
|
|
|
windowID: 0,
|
|
|
|
uri: this.browser.currentURI.spec,
|
|
|
|
title: this.browser.contentTitle
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
if (/^about:/.test(target.documentURI)) {
|
|
|
|
this.browser.addEventListener("click", ErrorPageEventHandler, false);
|
|
|
|
this.browser.addEventListener("pagehide", function listener() {
|
|
|
|
this.browser.removeEventListener("click", ErrorPageEventHandler, false);
|
|
|
|
this.browser.removeEventListener("pagehide", listener, true);
|
2011-12-08 05:56:41 -08:00
|
|
|
}.bind(this), true);
|
2011-12-06 10:53:10 -08:00
|
|
|
}
|
|
|
|
|
2012-01-31 12:49:25 -08:00
|
|
|
// Show a plugin doorhanger if there are plugins on the page but no
|
|
|
|
// clickable overlays showing (this doesn't work on pages loaded after
|
|
|
|
// back/forward navigation - see bug 719875)
|
|
|
|
if (this._pluginCount && !this._pluginOverlayShowing)
|
2011-12-09 14:04:19 -08:00
|
|
|
PluginHelper.showDoorHanger(this);
|
|
|
|
|
2011-12-06 10:53:10 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "DOMLinkAdded": {
|
|
|
|
let target = aEvent.originalTarget;
|
|
|
|
if (!target.href || target.disabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// ignore on frames
|
2011-12-08 05:56:41 -08:00
|
|
|
if (target.ownerDocument.defaultView != this.browser.contentWindow)
|
2011-12-06 10:53:10 -08:00
|
|
|
return;
|
|
|
|
|
2011-12-08 05:56:43 -08:00
|
|
|
// sanitize the rel string
|
|
|
|
let list = [];
|
|
|
|
if (target.rel) {
|
|
|
|
list = target.rel.toLowerCase().split(/\s+/);
|
|
|
|
let hash = {};
|
|
|
|
list.forEach(function(value) { hash[value] = true; });
|
|
|
|
list = [];
|
|
|
|
for (let rel in hash)
|
|
|
|
list.push("[" + rel + "]");
|
|
|
|
}
|
|
|
|
|
2011-12-06 10:53:10 -08:00
|
|
|
let json = {
|
|
|
|
type: "DOMLinkAdded",
|
|
|
|
tabID: this.id,
|
|
|
|
href: resolveGeckoURI(target.href),
|
|
|
|
charset: target.ownerDocument.characterSet,
|
|
|
|
title: target.title,
|
2011-12-08 05:56:43 -08:00
|
|
|
rel: list.join(" ")
|
2011-12-06 10:53:10 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// rel=icon can also have a sizes attribute
|
|
|
|
if (target.hasAttribute("sizes"))
|
|
|
|
json.sizes = target.getAttribute("sizes");
|
|
|
|
|
|
|
|
sendMessageToJava({ gecko: json });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "DOMTitleChanged": {
|
|
|
|
if (!aEvent.isTrusted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// ignore on frames
|
|
|
|
if (aEvent.target.defaultView != this.browser.contentWindow)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "DOMTitleChanged",
|
|
|
|
tabID: this.id,
|
2012-02-01 19:44:18 -08:00
|
|
|
title: aEvent.target.title.substring(0, 255)
|
2011-12-06 10:53:10 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-12-30 11:48:21 -08:00
|
|
|
case "DOMWindowClose": {
|
|
|
|
if (!aEvent.isTrusted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Find the relevant tab, and close it from Java
|
|
|
|
if (this.browser.contentWindow == aEvent.target) {
|
|
|
|
aEvent.preventDefault();
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "DOMWindowClose",
|
|
|
|
tabID: this.id
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-12-06 10:50:21 -08:00
|
|
|
case "scroll": {
|
|
|
|
let win = this.browser.contentWindow;
|
|
|
|
if (this.userScrollPos.x != win.scrollX || this.userScrollPos.y != win.scrollY) {
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Viewport:UpdateLater"
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2011-12-09 14:04:19 -08:00
|
|
|
|
|
|
|
case "PluginClickToPlay": {
|
2012-01-31 12:49:25 -08:00
|
|
|
// Keep track of the number of plugins to know whether or not to show
|
|
|
|
// the hidden plugins doorhanger
|
|
|
|
this._pluginCount++;
|
2011-12-20 13:26:01 -08:00
|
|
|
|
2012-01-31 12:49:25 -08:00
|
|
|
let plugin = aEvent.target;
|
2012-01-06 11:57:45 -08:00
|
|
|
let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox");
|
2011-12-09 14:04:19 -08:00
|
|
|
if (!overlay)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If the overlay is too small, hide the overlay and act like this
|
|
|
|
// is a hidden plugin object
|
|
|
|
if (PluginHelper.isTooSmall(plugin, overlay)) {
|
|
|
|
overlay.style.visibility = "hidden";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-01-31 12:49:25 -08:00
|
|
|
// Add click to play listener to the overlay
|
|
|
|
overlay.addEventListener("click", (function(event) {
|
2012-01-06 11:57:45 -08:00
|
|
|
// Play all the plugin objects when the user clicks on one
|
|
|
|
PluginHelper.playAllPlugins(this, event);
|
|
|
|
}).bind(this), true);
|
|
|
|
|
2011-12-09 14:04:19 -08:00
|
|
|
this._pluginOverlayShowing = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "pagehide": {
|
2011-12-20 13:26:01 -08:00
|
|
|
// Check to make sure it's top-level pagehide
|
|
|
|
if (aEvent.target.defaultView == this.browser.contentWindow) {
|
|
|
|
// Reset plugin state when we leave the page
|
2012-01-31 12:49:25 -08:00
|
|
|
this._pluginCount = 0;
|
2011-12-20 13:26:01 -08:00
|
|
|
this._pluginOverlayShowing = false;
|
|
|
|
}
|
2011-12-09 14:04:19 -08:00
|
|
|
break;
|
|
|
|
}
|
2011-12-06 10:50:21 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-10-12 14:31:04 -07:00
|
|
|
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
|
2012-02-08 11:23:32 -08:00
|
|
|
let contentWin = aWebProgress.DOMWindow;
|
|
|
|
if (contentWin != contentWin.top)
|
|
|
|
return;
|
|
|
|
|
2011-12-21 13:34:02 -08:00
|
|
|
if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
|
|
|
|
// Filter optimization: Only really send NETWORK state changes to Java listener
|
2011-10-24 19:00:52 -07:00
|
|
|
let browser = BrowserApp.getBrowserForWindow(aWebProgress.DOMWindow);
|
|
|
|
let uri = "";
|
|
|
|
if (browser)
|
|
|
|
uri = browser.currentURI.spec;
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2012-02-08 11:23:32 -08:00
|
|
|
// Check to see if we restoring the content from a previous presentation (session)
|
|
|
|
// since there should be no real network activity
|
|
|
|
let restoring = aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING;
|
|
|
|
let showProgress = restoring ? false : this.showProgress;
|
|
|
|
|
2011-10-24 19:00:52 -07:00
|
|
|
let message = {
|
|
|
|
gecko: {
|
2011-11-03 15:53:34 -07:00
|
|
|
type: "Content:StateChange",
|
2011-10-24 19:00:52 -07:00
|
|
|
tabID: this.id,
|
|
|
|
uri: uri,
|
2012-01-19 11:44:16 -08:00
|
|
|
state: aStateFlags,
|
2012-02-08 11:23:32 -08:00
|
|
|
showProgress: showProgress
|
2011-10-24 19:00:52 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
sendMessageToJava(message);
|
2012-01-19 11:44:16 -08:00
|
|
|
|
|
|
|
// Reset showProgress after state change
|
|
|
|
this.showProgress = true;
|
2011-10-24 19:00:52 -07:00
|
|
|
}
|
2011-10-12 14:31:04 -07:00
|
|
|
},
|
2011-10-10 17:40:17 -07:00
|
|
|
|
2011-11-29 06:58:43 -08:00
|
|
|
onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) {
|
2011-10-25 15:01:20 -07:00
|
|
|
let contentWin = aWebProgress.DOMWindow;
|
|
|
|
if (contentWin != contentWin.top)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(contentWin);
|
2011-10-14 13:07:17 -07:00
|
|
|
let uri = browser.currentURI.spec;
|
2011-12-14 13:36:29 -08:00
|
|
|
let documentURI = "";
|
|
|
|
let contentType = "";
|
|
|
|
if (browser.contentDocument) {
|
|
|
|
documentURI = browser.contentDocument.documentURIObject.spec;
|
|
|
|
contentType = browser.contentDocument.contentType;
|
|
|
|
}
|
2011-10-14 13:07:17 -07:00
|
|
|
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
2011-11-03 15:53:34 -07:00
|
|
|
type: "Content:LocationChange",
|
2011-10-14 23:22:25 -07:00
|
|
|
tabID: this.id,
|
2011-12-13 16:23:35 -08:00
|
|
|
uri: uri,
|
|
|
|
documentURI: documentURI,
|
|
|
|
contentType: contentType
|
2011-10-14 13:07:17 -07:00
|
|
|
}
|
|
|
|
};
|
2011-10-12 14:31:04 -07:00
|
|
|
|
2011-10-14 13:07:17 -07:00
|
|
|
sendMessageToJava(message);
|
2011-11-29 06:58:43 -08:00
|
|
|
|
|
|
|
if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) == 0) {
|
2011-12-01 06:36:41 -08:00
|
|
|
this.updateViewport(true);
|
|
|
|
} else {
|
|
|
|
this.sendViewportUpdate();
|
2011-11-29 06:58:43 -08:00
|
|
|
}
|
2011-10-12 14:31:04 -07:00
|
|
|
},
|
|
|
|
|
2011-11-03 15:53:34 -07:00
|
|
|
onSecurityChange: function(aWebProgress, aRequest, aState) {
|
|
|
|
let mode = "unknown";
|
|
|
|
if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
|
|
|
|
mode = "identified";
|
|
|
|
else if (aState & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
|
|
|
|
mode = "verified";
|
|
|
|
else if (aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN)
|
|
|
|
mode = "mixed";
|
|
|
|
else
|
|
|
|
mode = "unknown";
|
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
let message = {
|
2011-11-03 15:53:34 -07:00
|
|
|
gecko: {
|
|
|
|
type: "Content:SecurityChange",
|
|
|
|
tabID: this.id,
|
|
|
|
mode: mode
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
sendMessageToJava(message);
|
2011-10-12 14:31:04 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
|
|
|
|
},
|
2011-10-24 19:00:52 -07:00
|
|
|
|
2011-10-12 14:31:04 -07:00
|
|
|
onStatusChange: function(aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
|
|
|
|
},
|
|
|
|
|
2011-10-31 08:02:20 -07:00
|
|
|
_sendHistoryEvent: function(aMessage, aIndex, aUri) {
|
|
|
|
let message = {
|
|
|
|
gecko: {
|
|
|
|
type: "SessionHistory:" + aMessage,
|
|
|
|
tabID: this.id,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (aIndex != -1) {
|
|
|
|
message.gecko.index = aIndex;
|
|
|
|
}
|
|
|
|
if (aUri != null) {
|
|
|
|
message.gecko.uri = aUri;
|
|
|
|
}
|
|
|
|
sendMessageToJava(message);
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryNewEntry: function(aUri) {
|
|
|
|
this._sendHistoryEvent("New", -1, aUri.spec);
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryGoBack: function(aUri) {
|
|
|
|
this._sendHistoryEvent("Back", -1, null);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryGoForward: function(aUri) {
|
|
|
|
this._sendHistoryEvent("Forward", -1, null);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryReload: function(aUri, aFlags) {
|
|
|
|
// we don't do anything with this, so don't propagate it
|
|
|
|
// for now anyway
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryGotoIndex: function(aIndex, aUri) {
|
|
|
|
this._sendHistoryEvent("Goto", aIndex, null);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
OnHistoryPurge: function(aNumEntries) {
|
|
|
|
this._sendHistoryEvent("Purge", -1, null);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
get metadata() {
|
2012-01-06 16:42:44 -08:00
|
|
|
return ViewportHandler.getMetadataForDocument(this.browser.contentDocument);
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/** Update viewport when the metadata changes. */
|
|
|
|
updateViewportMetadata: function updateViewportMetadata(aMetadata) {
|
|
|
|
if (aMetadata && aMetadata.autoScale) {
|
|
|
|
let scaleRatio = aMetadata.scaleRatio = ViewportHandler.getScaleRatio();
|
|
|
|
|
|
|
|
if ("defaultZoom" in aMetadata && aMetadata.defaultZoom > 0)
|
|
|
|
aMetadata.defaultZoom *= scaleRatio;
|
|
|
|
if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
|
|
|
|
aMetadata.minZoom *= scaleRatio;
|
|
|
|
if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0)
|
|
|
|
aMetadata.maxZoom *= scaleRatio;
|
|
|
|
}
|
2012-01-06 16:42:44 -08:00
|
|
|
ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
|
2011-11-29 09:35:26 -08:00
|
|
|
this.updateViewportSize();
|
2011-12-01 06:36:41 -08:00
|
|
|
this.updateViewport(true);
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/** Update viewport when the metadata or the window size changes. */
|
|
|
|
updateViewportSize: function updateViewportSize() {
|
|
|
|
let browser = this.browser;
|
|
|
|
if (!browser)
|
|
|
|
return;
|
|
|
|
|
2011-12-02 10:11:40 -08:00
|
|
|
let screenW = this._viewport.width;
|
|
|
|
let screenH = this._viewport.height;
|
2011-11-29 09:35:26 -08:00
|
|
|
let viewportW, viewportH;
|
|
|
|
|
|
|
|
let metadata = this.metadata;
|
|
|
|
if (metadata.autoSize) {
|
|
|
|
if ("scaleRatio" in metadata) {
|
|
|
|
viewportW = screenW / metadata.scaleRatio;
|
|
|
|
viewportH = screenH / metadata.scaleRatio;
|
|
|
|
} else {
|
|
|
|
viewportW = screenW;
|
|
|
|
viewportH = screenH;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
viewportW = metadata.width;
|
|
|
|
viewportH = metadata.height;
|
|
|
|
|
|
|
|
// If (scale * width) < device-width, increase the width (bug 561413).
|
|
|
|
let maxInitialZoom = metadata.defaultZoom || metadata.maxZoom;
|
|
|
|
if (maxInitialZoom && viewportW)
|
|
|
|
viewportW = Math.max(viewportW, screenW / maxInitialZoom);
|
|
|
|
|
|
|
|
let validW = viewportW > 0;
|
|
|
|
let validH = viewportH > 0;
|
|
|
|
|
|
|
|
if (!validW)
|
|
|
|
viewportW = validH ? (viewportH * (screenW / screenH)) : BrowserApp.defaultBrowserWidth;
|
|
|
|
if (!validH)
|
|
|
|
viewportH = viewportW * (screenH / screenW);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the viewport height is not shorter than the window when
|
|
|
|
// the page is zoomed out to show its full width.
|
|
|
|
let minScale = this.getPageZoomLevel(screenW);
|
|
|
|
viewportH = Math.max(viewportH, screenH / minScale);
|
|
|
|
|
2012-02-20 09:21:56 -08:00
|
|
|
let oldBrowserWidth = parseInt(this.browser.style.minWidth);
|
2011-11-29 09:35:26 -08:00
|
|
|
this.setBrowserSize(viewportW, viewportH);
|
2012-01-20 21:14:03 -08:00
|
|
|
|
|
|
|
// Avoid having the scroll position jump around after device rotation.
|
|
|
|
let win = this.browser.contentWindow;
|
|
|
|
this.userScrollPos.x = win.scrollX;
|
|
|
|
this.userScrollPos.y = win.scrollY;
|
|
|
|
|
|
|
|
// If the browser width changes, we change the zoom proportionally. This ensures sensible
|
|
|
|
// behavior when rotating the device on pages with automatically-resizing viewports.
|
|
|
|
|
|
|
|
if (viewportW == oldBrowserWidth)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let viewport = this.viewport;
|
|
|
|
let newZoom = oldBrowserWidth * viewport.zoom / viewportW;
|
|
|
|
this.updateViewport(true, newZoom);
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
getDefaultZoomLevel: function getDefaultZoomLevel() {
|
|
|
|
let md = this.metadata;
|
|
|
|
if ("defaultZoom" in md && md.defaultZoom)
|
|
|
|
return md.defaultZoom;
|
|
|
|
|
2012-02-20 09:21:56 -08:00
|
|
|
let browserWidth = parseInt(this.browser.style.minWidth);
|
2011-12-07 19:21:03 -08:00
|
|
|
return gScreenWidth / browserWidth;
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
getPageZoomLevel: function getPageZoomLevel() {
|
2011-11-30 17:03:24 -08:00
|
|
|
// This may get called during a Viewport:Change message while the document
|
|
|
|
// has not loaded yet.
|
|
|
|
if (!this.browser.contentDocument || !this.browser.contentDocument.body)
|
2011-11-29 14:45:18 -08:00
|
|
|
return 1.0;
|
2011-11-30 17:03:24 -08:00
|
|
|
|
2011-12-02 10:11:40 -08:00
|
|
|
return this._viewport.width / this.browser.contentDocument.body.clientWidth;
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
setBrowserSize: function(aWidth, aHeight) {
|
2012-02-20 09:21:56 -08:00
|
|
|
// Using min width/height so as not to conflict with the fullscreen style rule.
|
|
|
|
// See Bug #709813.
|
|
|
|
this.browser.style.minWidth = aWidth + "px";
|
|
|
|
this.browser.style.minHeight = aHeight + "px";
|
2011-11-29 09:35:26 -08:00
|
|
|
},
|
|
|
|
|
2011-11-29 10:43:29 -08:00
|
|
|
getRequestLoadContext: function(aRequest) {
|
|
|
|
if (aRequest && aRequest.notificationCallbacks) {
|
|
|
|
try {
|
|
|
|
return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
|
|
} catch (ex) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aRequest && aRequest.loadGroup && aRequest.loadGroup.notificationCallbacks) {
|
|
|
|
try {
|
|
|
|
return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
|
|
} catch (ex) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
getWindowForRequest: function(aRequest) {
|
|
|
|
let loadContext = this.getRequestLoadContext(aRequest);
|
|
|
|
if (loadContext)
|
|
|
|
return loadContext.associatedWindow;
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
2012-01-06 16:42:46 -08:00
|
|
|
switch (aTopic) {
|
|
|
|
case "document-shown":
|
|
|
|
// Is it on the top level?
|
|
|
|
let contentDocument = aSubject;
|
|
|
|
if (contentDocument == this.browser.contentDocument) {
|
|
|
|
ViewportHandler.updateMetadata(this);
|
|
|
|
this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
|
|
|
|
}
|
|
|
|
break;
|
2011-11-29 10:43:29 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([
|
|
|
|
Ci.nsIWebProgressListener,
|
|
|
|
Ci.nsISHistoryListener,
|
|
|
|
Ci.nsIObserver,
|
|
|
|
Ci.nsISupportsWeakReference
|
|
|
|
])
|
2011-10-10 17:40:17 -07:00
|
|
|
};
|
2010-04-08 07:43:04 -07:00
|
|
|
|
2011-10-13 13:06:41 -07:00
|
|
|
var BrowserEventHandler = {
|
|
|
|
init: function init() {
|
2011-11-30 13:10:25 -08:00
|
|
|
Services.obs.addObserver(this, "Gesture:SingleTap", false);
|
2012-03-05 12:01:48 -08:00
|
|
|
Services.obs.addObserver(this, "Gesture:ShowPress", false);
|
2011-11-30 13:10:25 -08:00
|
|
|
Services.obs.addObserver(this, "Gesture:CancelTouch", false);
|
2011-11-15 13:41:19 -08:00
|
|
|
Services.obs.addObserver(this, "Gesture:DoubleTap", false);
|
2011-12-06 07:13:14 -08:00
|
|
|
Services.obs.addObserver(this, "Gesture:Scroll", false);
|
2012-01-24 16:31:33 -08:00
|
|
|
Services.obs.addObserver(this, "dom-touch-listener-added", false);
|
2011-11-30 13:10:25 -08:00
|
|
|
|
2011-11-03 23:54:47 -07:00
|
|
|
BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
|
2011-10-13 13:06:41 -07:00
|
|
|
},
|
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
observe: function(aSubject, aTopic, aData) {
|
2011-12-06 07:13:14 -08:00
|
|
|
if (aTopic == "Gesture:Scroll") {
|
|
|
|
// If we've lost our scrollable element, return. Don't cancel the
|
|
|
|
// override, as we probably don't want Java to handle panning until the
|
|
|
|
// user releases their finger.
|
|
|
|
if (this._scrollableElement == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If this is the first scroll event and we can't scroll in the direction
|
|
|
|
// the user wanted, and neither can any non-root sub-frame, cancel the
|
|
|
|
// override so that Java can handle panning the main document.
|
|
|
|
let data = JSON.parse(aData);
|
|
|
|
if (this._firstScrollEvent) {
|
2011-12-08 12:42:55 -08:00
|
|
|
while (this._scrollableElement != null && !this._elementCanScroll(this._scrollableElement, data.x, data.y))
|
2011-12-06 07:13:14 -08:00
|
|
|
this._scrollableElement = this._findScrollableElement(this._scrollableElement, false);
|
|
|
|
|
|
|
|
let doc = BrowserApp.selectedBrowser.contentDocument;
|
2011-12-08 12:42:55 -08:00
|
|
|
if (this._scrollableElement == doc.body || this._scrollableElement == doc.documentElement) {
|
2011-12-06 07:13:14 -08:00
|
|
|
sendMessageToJava({ gecko: { type: "Panning:CancelOverride" } });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._firstScrollEvent = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll the scrollable element
|
2012-01-21 09:44:44 -08:00
|
|
|
if (this._elementCanScroll(this._scrollableElement, data.x, data.y)) {
|
|
|
|
this._scrollElementBy(this._scrollableElement, data.x, data.y);
|
|
|
|
sendMessageToJava({ gecko: { type: "Gesture:ScrollAck", scrolled: true } });
|
|
|
|
} else {
|
|
|
|
sendMessageToJava({ gecko: { type: "Gesture:ScrollAck", scrolled: false } });
|
|
|
|
}
|
2011-12-06 07:13:14 -08:00
|
|
|
} else if (aTopic == "Gesture:CancelTouch") {
|
2011-11-30 13:10:25 -08:00
|
|
|
this._cancelTapHighlight();
|
2012-03-05 12:01:48 -08:00
|
|
|
} else if (aTopic == "Gesture:ShowPress") {
|
|
|
|
let data = JSON.parse(aData);
|
|
|
|
let closest = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y);
|
|
|
|
if (!closest)
|
|
|
|
closest = ElementTouchHelper.anyElementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y);
|
|
|
|
if (closest) {
|
|
|
|
this._doTapHighlight(closest);
|
|
|
|
|
|
|
|
// If we've pressed a scrollable element, let Java know that we may
|
|
|
|
// want to override the scroll behaviour (for document sub-frames)
|
|
|
|
this._scrollableElement = this._findScrollableElement(closest, true);
|
|
|
|
this._firstScrollEvent = true;
|
|
|
|
|
|
|
|
if (this._scrollableElement != null) {
|
|
|
|
// Discard if it's the top-level scrollable, we let Java handle this
|
|
|
|
let doc = BrowserApp.selectedBrowser.contentDocument;
|
|
|
|
if (this._scrollableElement != doc.body && this._scrollableElement != doc.documentElement)
|
|
|
|
sendMessageToJava({ gecko: { type: "Panning:Override" } });
|
|
|
|
}
|
|
|
|
}
|
2011-11-30 13:10:25 -08:00
|
|
|
} else if (aTopic == "Gesture:SingleTap") {
|
|
|
|
let element = this._highlightElement;
|
2012-03-01 10:58:19 -08:00
|
|
|
if (element && !SelectHelper.handleClick(element)) {
|
2012-01-12 17:01:32 -08:00
|
|
|
try {
|
|
|
|
let data = JSON.parse(aData);
|
|
|
|
[data.x, data.y] = ElementTouchHelper.toScreenCoords(element.ownerDocument.defaultView, data.x, data.y);
|
|
|
|
|
|
|
|
this._sendMouseEvent("mousemove", element, data.x, data.y);
|
|
|
|
this._sendMouseEvent("mousedown", element, data.x, data.y);
|
|
|
|
this._sendMouseEvent("mouseup", element, data.x, data.y);
|
|
|
|
|
|
|
|
if (ElementTouchHelper.isElementClickable(element))
|
|
|
|
Haptic.performSimpleAction(Haptic.LongPress);
|
|
|
|
} catch(e) {
|
|
|
|
Cu.reportError(e);
|
|
|
|
}
|
2011-11-30 13:10:25 -08:00
|
|
|
}
|
|
|
|
this._cancelTapHighlight();
|
2011-11-15 13:41:19 -08:00
|
|
|
} else if (aTopic == "Gesture:DoubleTap") {
|
|
|
|
this._cancelTapHighlight();
|
|
|
|
this.onDoubleTap(aData);
|
2012-01-24 16:31:33 -08:00
|
|
|
} else if (aTopic == "dom-touch-listener-added") {
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(aSubject);
|
|
|
|
if (!browser)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
|
|
|
if (!tab)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Tab:HasTouchListener",
|
|
|
|
tabID: tab.id
|
|
|
|
}
|
|
|
|
});
|
2011-11-15 13:41:19 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_zoomOut: function() {
|
2012-03-05 09:06:50 -08:00
|
|
|
sendMessageToJava({ gecko: { type: "Browser:ZoomToPageWidth"} });
|
2011-11-15 13:41:19 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
onDoubleTap: function(aData) {
|
|
|
|
let data = JSON.parse(aData);
|
|
|
|
|
|
|
|
let win = BrowserApp.selectedBrowser.contentWindow;
|
|
|
|
|
|
|
|
let zoom = BrowserApp.selectedTab._viewport.zoom;
|
|
|
|
let element = ElementTouchHelper.anyElementFromPoint(win, data.x, data.y);
|
|
|
|
if (!element) {
|
|
|
|
this._zoomOut();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
win = element.ownerDocument.defaultView;
|
|
|
|
while (element && win.getComputedStyle(element,null).display == "inline")
|
|
|
|
element = element.parentNode;
|
2012-03-05 09:06:50 -08:00
|
|
|
|
|
|
|
if (!element) {
|
2011-11-15 13:41:19 -08:00
|
|
|
this._zoomOut();
|
2012-03-05 09:06:50 -08:00
|
|
|
} else {
|
2011-11-15 13:41:19 -08:00
|
|
|
const margin = 15;
|
2012-03-05 09:06:50 -08:00
|
|
|
const minDifference = -20;
|
|
|
|
const maxDifference = 20;
|
|
|
|
let rect = ElementTouchHelper.getBoundingContentRect(element);
|
|
|
|
|
|
|
|
let viewport = BrowserApp.selectedTab.viewport;
|
|
|
|
let vRect = new Rect(viewport.x, viewport.y, viewport.width, viewport.height);
|
|
|
|
|
|
|
|
let zoom = viewport.zoom;
|
|
|
|
let bRect = new Rect(Math.max(0,rect.x - margin),
|
|
|
|
rect.y,
|
|
|
|
rect.w + 2*margin,
|
|
|
|
rect.h);
|
|
|
|
// constrict the rect to the screen width
|
|
|
|
bRect.width = Math.min(bRect.width, viewport.pageWidth/zoom - bRect.x);
|
|
|
|
bRect.scale(zoom, zoom);
|
|
|
|
|
|
|
|
let overlap = vRect.intersect(bRect);
|
|
|
|
let overlapArea = overlap.width*overlap.height;
|
|
|
|
// we want to know if the area of the element showing is near the max we can show
|
|
|
|
// on the screen at any time and if its already stretching the width of the screen
|
|
|
|
let availHeight = Math.min(bRect.width*vRect.height/vRect.width, bRect.height);
|
|
|
|
let showing = overlapArea/(bRect.width*availHeight);
|
|
|
|
let dw = (bRect.width - vRect.width)/zoom;
|
|
|
|
let dx = (bRect.x - vRect.x)/zoom;
|
|
|
|
|
|
|
|
if (showing > 0.9 &&
|
|
|
|
dx > minDifference && dx < maxDifference &&
|
|
|
|
dw > minDifference && dw < maxDifference) {
|
|
|
|
this._zoomOut();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
rect.type = "Browser:ZoomToRect";
|
|
|
|
rect.x = bRect.x; rect.y = bRect.y;
|
|
|
|
rect.w = bRect.width; rect.h = availHeight;
|
|
|
|
sendMessageToJava({ gecko: rect });
|
2011-11-30 13:10:25 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-12-06 07:13:14 -08:00
|
|
|
_firstScrollEvent: false,
|
|
|
|
|
|
|
|
_scrollableElement: null,
|
|
|
|
|
|
|
|
_highlightElement: null,
|
2011-11-30 13:10:25 -08:00
|
|
|
|
|
|
|
_doTapHighlight: function _doTapHighlight(aElement) {
|
|
|
|
DOMUtils.setContentState(aElement, kStateActive);
|
|
|
|
this._highlightElement = aElement;
|
|
|
|
},
|
|
|
|
|
|
|
|
_cancelTapHighlight: function _cancelTapHighlight() {
|
|
|
|
DOMUtils.setContentState(BrowserApp.selectedBrowser.contentWindow.document.documentElement, kStateActive);
|
|
|
|
this._highlightElement = null;
|
|
|
|
},
|
|
|
|
|
2011-10-15 14:44:05 -07:00
|
|
|
_updateLastPosition: function(x, y, dx, dy) {
|
2011-10-12 14:31:04 -07:00
|
|
|
this.lastX = x;
|
|
|
|
this.lastY = y;
|
2011-10-13 08:13:59 -07:00
|
|
|
this.lastTime = Date.now();
|
|
|
|
|
2011-10-19 09:17:59 -07:00
|
|
|
this.motionBuffer.push({ dx: dx, dy: dy, time: this.lastTime });
|
2011-10-15 14:44:05 -07:00
|
|
|
},
|
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
_sendMouseEvent: function _sendMouseEvent(aName, aElement, aX, aY, aButton) {
|
|
|
|
// the element can be out of the aX/aY point because of the touch radius
|
|
|
|
// if outside, we gracefully move the touch point to the center of the element
|
|
|
|
if (!(aElement instanceof HTMLHtmlElement)) {
|
|
|
|
let isTouchClick = true;
|
|
|
|
let rects = ElementTouchHelper.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.
|
2011-11-29 09:42:43 -08:00
|
|
|
let inBounds =
|
2011-10-28 17:10:31 -07:00
|
|
|
(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 = {x: rects[0].left, y: rects[0].top, w: rects[0].width, h: rects[0].height};
|
|
|
|
if (rect.w == 0 && rect.h == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let point = { x: rect.x + rect.w/2, y: rect.y + rect.h/2 };
|
|
|
|
aX = point.x;
|
|
|
|
aY = point.y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-12 17:01:32 -08:00
|
|
|
let window = aElement.ownerDocument.defaultView;
|
|
|
|
try {
|
|
|
|
[aX, aY] = ElementTouchHelper.toBrowserCoords(window, aX, aY);
|
|
|
|
let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
aButton = aButton || 0;
|
|
|
|
cwu.sendMouseEventToWindow(aName, Math.round(aX), Math.round(aY), aButton, 1, 0, true);
|
|
|
|
} catch(e) {
|
|
|
|
Cu.reportError(e);
|
|
|
|
}
|
2011-10-28 17:10:31 -07:00
|
|
|
},
|
|
|
|
|
2012-01-04 10:24:56 -08:00
|
|
|
_hasScrollableOverflow: function(elem) {
|
|
|
|
var win = elem.ownerDocument.defaultView;
|
|
|
|
if (!win)
|
|
|
|
return false;
|
|
|
|
var computedStyle = win.getComputedStyle(elem);
|
|
|
|
if (!computedStyle)
|
|
|
|
return false;
|
|
|
|
return computedStyle.overflow == 'auto' || computedStyle.overflow == 'scroll';
|
|
|
|
},
|
|
|
|
|
2011-10-15 14:44:05 -07:00
|
|
|
_findScrollableElement: function(elem, checkElem) {
|
|
|
|
// Walk the DOM tree until we find a scrollable element
|
|
|
|
let scrollable = false;
|
|
|
|
while (elem) {
|
|
|
|
/* Element is scrollable if its scroll-size exceeds its client size, and:
|
|
|
|
* - It has overflow 'auto' or 'scroll'
|
|
|
|
* - It's a textarea
|
2011-12-06 07:13:14 -08:00
|
|
|
* - It's an HTML/BODY node
|
2011-12-09 09:03:21 -08:00
|
|
|
* - It's a select element showing multiple rows
|
2011-10-15 14:44:05 -07:00
|
|
|
*/
|
|
|
|
if (checkElem) {
|
|
|
|
if (((elem.scrollHeight > elem.clientHeight) ||
|
|
|
|
(elem.scrollWidth > elem.clientWidth)) &&
|
2012-01-04 10:24:56 -08:00
|
|
|
(this._hasScrollableOverflow(elem) ||
|
2011-12-09 09:03:21 -08:00
|
|
|
elem.mozMatchesSelector("html, body, textarea")) ||
|
|
|
|
(elem instanceof HTMLSelectElement && (elem.size > 1 || elem.multiple))) {
|
2011-10-15 14:44:05 -07:00
|
|
|
scrollable = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
checkElem = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Propagate up iFrames
|
2011-12-08 12:42:55 -08:00
|
|
|
if (!elem.parentNode && elem.documentElement && elem.documentElement.ownerDocument)
|
2011-10-15 14:44:05 -07:00
|
|
|
elem = elem.documentElement.ownerDocument.defaultView.frameElement;
|
|
|
|
else
|
|
|
|
elem = elem.parentNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!scrollable)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
return elem;
|
|
|
|
},
|
|
|
|
|
2011-11-09 17:39:29 -08:00
|
|
|
_elementReceivesInput: function(aElement) {
|
|
|
|
return aElement instanceof Element &&
|
2011-11-21 20:15:05 -08:00
|
|
|
kElementsReceivingInput.hasOwnProperty(aElement.tagName.toLowerCase()) ||
|
|
|
|
this._isEditable(aElement);
|
|
|
|
},
|
|
|
|
|
|
|
|
_isEditable: function(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;
|
2011-11-09 17:39:29 -08:00
|
|
|
},
|
|
|
|
|
2011-10-15 14:44:05 -07:00
|
|
|
_scrollElementBy: function(elem, x, y) {
|
|
|
|
elem.scrollTop = elem.scrollTop + y;
|
|
|
|
elem.scrollLeft = elem.scrollLeft + x;
|
|
|
|
},
|
|
|
|
|
|
|
|
_elementCanScroll: function(elem, x, y) {
|
|
|
|
let scrollX = true;
|
|
|
|
let scrollY = true;
|
|
|
|
|
|
|
|
if (x < 0) {
|
|
|
|
if (elem.scrollLeft <= 0) {
|
|
|
|
scrollX = false;
|
|
|
|
}
|
|
|
|
} else if (elem.scrollLeft >= (elem.scrollWidth - elem.clientWidth)) {
|
|
|
|
scrollX = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y < 0) {
|
|
|
|
if (elem.scrollTop <= 0) {
|
|
|
|
scrollY = false;
|
|
|
|
}
|
|
|
|
} else if (elem.scrollTop >= (elem.scrollHeight - elem.clientHeight)) {
|
|
|
|
scrollY = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return scrollX || scrollY;
|
2011-10-12 14:31:04 -07:00
|
|
|
}
|
2010-12-29 07:40:51 -08:00
|
|
|
};
|
2011-10-26 13:21:51 -07:00
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
const kReferenceDpi = 240; // standard "pixel" size used in some preferences
|
|
|
|
|
|
|
|
const ElementTouchHelper = {
|
2011-11-30 13:10:25 -08:00
|
|
|
toBrowserCoords: function(aWindow, aX, aY) {
|
2012-01-12 17:01:32 -08:00
|
|
|
if (!aWindow)
|
|
|
|
throw "Must provide a window";
|
|
|
|
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(aWindow.top);
|
|
|
|
if (!browser)
|
|
|
|
throw "Unable to find a browser";
|
|
|
|
|
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
|
|
|
if (!tab)
|
|
|
|
throw "Unable to find a tab";
|
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
let viewport = tab.viewport;
|
|
|
|
return [
|
2012-01-12 17:01:32 -08:00
|
|
|
((aX - tab.viewportExcess.x) * viewport.zoom + viewport.offsetX),
|
|
|
|
((aY - tab.viewportExcess.y) * viewport.zoom + viewport.offsetY)
|
2011-11-30 13:10:25 -08:00
|
|
|
];
|
|
|
|
},
|
|
|
|
|
|
|
|
toScreenCoords: function(aWindow, aX, aY) {
|
2012-01-12 17:01:32 -08:00
|
|
|
if (!aWindow)
|
|
|
|
throw "Must provide a window";
|
|
|
|
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(aWindow.top);
|
|
|
|
if (!browser)
|
|
|
|
throw "Unable to find a browser";
|
|
|
|
|
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
|
|
|
if (!tab)
|
|
|
|
throw "Unable to find a tab";
|
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
let viewport = tab.viewport;
|
|
|
|
return [
|
|
|
|
(aX - viewport.offsetX)/viewport.zoom + tab.viewportExcess.x,
|
|
|
|
(aY - viewport.offsetY)/viewport.zoom + tab.viewportExcess.y
|
|
|
|
];
|
|
|
|
},
|
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
anyElementFromPoint: function(aWindow, aX, aY) {
|
2011-11-30 13:10:25 -08:00
|
|
|
[aX, aY] = this.toScreenCoords(aWindow, aX, aY);
|
2011-11-03 15:09:37 -07:00
|
|
|
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let elem = cwu.elementFromPoint(aX, aY, false, true);
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
|
|
|
|
let rect = elem.getBoundingClientRect();
|
|
|
|
aX -= rect.left;
|
|
|
|
aY -= rect.top;
|
|
|
|
cwu = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
elem = cwu.elementFromPoint(aX, aY, false, true);
|
|
|
|
}
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-03 15:09:37 -07:00
|
|
|
return elem;
|
|
|
|
},
|
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
elementFromPoint: function(aWindow, aX, aY) {
|
2011-11-30 13:10:25 -08:00
|
|
|
[aX, aY] = this.toScreenCoords(aWindow, aX, aY);
|
2011-10-28 17:10:31 -07:00
|
|
|
// browser's elementFromPoint expect browser-relative client coordinates.
|
|
|
|
// subtract browser's scroll values to adjust
|
|
|
|
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let elem = this.getClosest(cwu, aX, aY);
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
aX -= rect.left;
|
|
|
|
aY -= rect.top;
|
|
|
|
cwu = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
elem = ElementTouchHelper.getClosest(cwu, aX, aY);
|
|
|
|
}
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
return elem;
|
|
|
|
},
|
|
|
|
|
|
|
|
get radius() {
|
|
|
|
let prefs = Services.prefs;
|
|
|
|
delete this.radius;
|
|
|
|
return this.radius = { "top": prefs.getIntPref("browser.ui.touch.top"),
|
|
|
|
"right": prefs.getIntPref("browser.ui.touch.right"),
|
|
|
|
"bottom": prefs.getIntPref("browser.ui.touch.bottom"),
|
|
|
|
"left": prefs.getIntPref("browser.ui.touch.left")
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
get weight() {
|
|
|
|
delete this.weight;
|
|
|
|
return this.weight = { "visited": Services.prefs.getIntPref("browser.ui.touch.weight.visited") };
|
|
|
|
},
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
|
2011-11-30 13:10:25 -08:00
|
|
|
// if this element is clickable we return quickly
|
2011-12-08 12:42:55 -08:00
|
|
|
if (this.isElementClickable(target))
|
2011-11-30 13:10:25 -08:00
|
|
|
return target;
|
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
let target = null;
|
|
|
|
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];
|
2011-12-08 12:42:55 -08:00
|
|
|
if (!current.mozMatchesSelector || !this.isElementClickable(current))
|
2011-10-28 17:10:31 -07:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2011-12-08 12:42:55 -08:00
|
|
|
isElementClickable: function isElementClickable(aElement) {
|
2011-10-28 17:10:31 -07:00
|
|
|
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;
|
|
|
|
},
|
2011-12-02 08:04:09 -08:00
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
getContentClientRects: function(aElement) {
|
2011-12-02 08:04:09 -08:00
|
|
|
let offset = { x: 0, y: 0 };
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
let nativeRects = aElement.getClientRects();
|
|
|
|
// step out of iframes and frames, offsetting scroll values
|
2011-12-02 08:04:09 -08:00
|
|
|
for (let frame = aElement.ownerDocument.defaultView; frame.frameElement; frame = frame.parent) {
|
2011-10-28 17:10:31 -07:00
|
|
|
// 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.x += rect.left + parseInt(left);
|
|
|
|
offset.y += rect.top + parseInt(top);
|
|
|
|
}
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-10-28 17:10:31 -07:00
|
|
|
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;
|
2011-11-15 13:41:19 -08:00
|
|
|
},
|
2012-02-01 10:54:18 -08:00
|
|
|
|
2011-11-15 13:41:19 -08:00
|
|
|
getBoundingContentRect: function(aElement) {
|
|
|
|
if (!aElement)
|
|
|
|
return {x: 0, y: 0, w: 0, h: 0};
|
2012-02-01 10:54:18 -08:00
|
|
|
|
2011-11-15 13:41:19 -08:00
|
|
|
let document = aElement.ownerDocument;
|
|
|
|
while (document.defaultView.frameElement)
|
|
|
|
document = document.defaultView.frameElement.ownerDocument;
|
2012-02-01 10:54:18 -08:00
|
|
|
|
2011-11-15 13:41:19 -08:00
|
|
|
let cwu = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let scrollX = {}, scrollY = {};
|
|
|
|
cwu.getScrollXY(false, scrollX, scrollY);
|
2012-02-01 10:54:18 -08:00
|
|
|
|
2011-11-15 13:41:19 -08:00
|
|
|
let r = aElement.getBoundingClientRect();
|
2012-02-01 10:54:18 -08:00
|
|
|
|
2011-11-15 13:41:19 -08:00
|
|
|
// step out of iframes and frames, offsetting scroll values
|
|
|
|
for (let frame = aElement.ownerDocument.defaultView; frame.frameElement && 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;
|
|
|
|
scrollX.value += rect.left + parseInt(left);
|
|
|
|
scrollY.value += rect.top + parseInt(top);
|
|
|
|
}
|
|
|
|
|
2012-02-01 10:54:18 -08:00
|
|
|
return {x: r.left + scrollX.value,
|
|
|
|
y: r.top + scrollY.value,
|
|
|
|
w: r.width,
|
|
|
|
h: r.height };
|
2011-10-28 17:10:31 -07:00
|
|
|
}
|
|
|
|
};
|
2011-10-27 21:55:12 -07:00
|
|
|
|
|
|
|
var ErrorPageEventHandler = {
|
|
|
|
handleEvent: function(aEvent) {
|
|
|
|
switch (aEvent.type) {
|
|
|
|
case "click": {
|
|
|
|
// Don't trust synthetic events
|
|
|
|
if (!aEvent.isTrusted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let target = aEvent.originalTarget;
|
|
|
|
let errorDoc = target.ownerDocument;
|
|
|
|
|
|
|
|
// If the event came from an ssl error page, it is probably either the "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 (target == temp || target == perm) {
|
|
|
|
// Handle setting an cert exception and reloading the page
|
|
|
|
try {
|
|
|
|
// Add a new SSL exception for this URL
|
|
|
|
let uri = Services.io.newURI(errorDoc.location.href, null, null);
|
|
|
|
let sslExceptions = new SSLExceptions();
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-10-27 21:55:12 -07:00
|
|
|
if (target == perm)
|
|
|
|
sslExceptions.addPermanentException(uri);
|
|
|
|
else
|
|
|
|
sslExceptions.addTemporaryException(uri);
|
|
|
|
} catch (e) {
|
|
|
|
dump("Failed to set cert exception: " + e + "\n");
|
|
|
|
}
|
|
|
|
errorDoc.location.reload();
|
|
|
|
} else if (target == errorDoc.getElementById("getMeOutOfHereButton")) {
|
2011-12-08 13:42:00 -08:00
|
|
|
errorDoc.location = "about:home";
|
2011-10-27 21:55:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-10-28 11:49:19 -07:00
|
|
|
var FormAssistant = {
|
2011-12-06 16:44:08 -08:00
|
|
|
// Used to keep track of the element that corresponds to the current
|
|
|
|
// autocomplete suggestions
|
|
|
|
_currentInputElement: null,
|
|
|
|
|
|
|
|
init: function() {
|
|
|
|
Services.obs.addObserver(this, "FormAssist:AutoComplete", false);
|
|
|
|
Services.obs.addObserver(this, "FormAssist:Closed", false);
|
|
|
|
|
2012-01-10 16:25:23 -08:00
|
|
|
BrowserApp.deck.addEventListener("input", this, false);
|
2011-12-06 16:44:08 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function() {
|
|
|
|
Services.obs.removeObserver(this, "FormAssist:AutoComplete");
|
|
|
|
Services.obs.removeObserver(this, "FormAssist:Closed");
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
switch (aTopic) {
|
|
|
|
case "FormAssist:AutoComplete":
|
|
|
|
if (!this._currentInputElement)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Remove focus from the textbox to avoid some bad IME interactions
|
|
|
|
this._currentInputElement.blur();
|
|
|
|
this._currentInputElement.value = aData;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "FormAssist:Closed":
|
|
|
|
this._currentInputElement = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function(aEvent) {
|
2012-01-10 16:25:23 -08:00
|
|
|
switch (aEvent.type) {
|
|
|
|
case "input":
|
2011-12-06 16:44:08 -08:00
|
|
|
let currentElement = aEvent.target;
|
|
|
|
if (!this._isAutocomplete(currentElement))
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Keep track of input element so we can fill it in if the user
|
|
|
|
// selects an autocomplete suggestion
|
|
|
|
this._currentInputElement = currentElement;
|
2012-01-10 16:25:23 -08:00
|
|
|
let suggestions = this._getAutocompleteSuggestions(currentElement.value, currentElement);
|
2011-12-06 16:44:08 -08:00
|
|
|
|
2012-02-01 10:54:42 -08:00
|
|
|
let rect = ElementTouchHelper.getBoundingContentRect(currentElement);
|
|
|
|
let viewport = BrowserApp.selectedTab.viewport;
|
2011-12-06 16:44:08 -08:00
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "FormAssist:AutoComplete",
|
|
|
|
suggestions: suggestions,
|
2012-02-01 10:54:42 -08:00
|
|
|
rect: [rect.x - (viewport.x / viewport.zoom), rect.y - (viewport.y / viewport.zoom), rect.w, rect.h],
|
|
|
|
zoom: viewport.zoom
|
2011-12-06 16:44:08 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_isAutocomplete: function (aElement) {
|
|
|
|
if (!(aElement instanceof HTMLInputElement) ||
|
|
|
|
(aElement.getAttribute("type") == "password") ||
|
|
|
|
(aElement.hasAttribute("autocomplete") &&
|
|
|
|
aElement.getAttribute("autocomplete").toLowerCase() == "off"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/** Retrieve the autocomplete list from the autocomplete service for an element */
|
|
|
|
_getAutocompleteSuggestions: function(aSearchString, aElement) {
|
|
|
|
let results = Cc["@mozilla.org/satchel/form-autocomplete;1"].
|
|
|
|
getService(Ci.nsIFormAutoComplete).
|
|
|
|
autoCompleteSearch(aElement.name || aElement.id, aSearchString, aElement, null);
|
|
|
|
|
|
|
|
let suggestions = [];
|
|
|
|
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 == aSearchString)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
suggestions.push(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return suggestions;
|
2011-10-28 11:49:19 -07:00
|
|
|
}
|
2012-03-01 10:58:19 -08:00
|
|
|
};
|
2011-10-28 11:49:19 -07:00
|
|
|
|
2011-10-26 13:21:51 -07:00
|
|
|
var XPInstallObserver = {
|
2011-11-21 22:12:59 -08:00
|
|
|
init: function xpi_init() {
|
|
|
|
Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
|
|
|
|
Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
|
|
|
|
|
|
|
|
AddonManager.addInstallListener(XPInstallObserver);
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function xpi_uninit() {
|
|
|
|
Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
|
|
|
|
Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
|
|
|
|
|
|
|
|
AddonManager.removeInstallListener(XPInstallObserver);
|
|
|
|
},
|
|
|
|
|
2011-10-26 13:21:51 -07:00
|
|
|
observe: function xpi_observer(aSubject, aTopic, aData) {
|
|
|
|
switch (aTopic) {
|
|
|
|
case "addon-install-started":
|
|
|
|
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsDownloading"), "short");
|
|
|
|
break;
|
|
|
|
case "addon-install-blocked":
|
|
|
|
let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
|
|
|
|
let host = installInfo.originatingURI.host;
|
|
|
|
|
|
|
|
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
|
2011-11-21 22:12:59 -08:00
|
|
|
let notificationName, buttons, message;
|
2011-10-26 13:21:51 -07:00
|
|
|
let strings = Strings.browser;
|
|
|
|
let enabled = true;
|
|
|
|
try {
|
|
|
|
enabled = Services.prefs.getBoolPref("xpinstall.enabled");
|
|
|
|
}
|
|
|
|
catch (e) {}
|
|
|
|
|
|
|
|
if (!enabled) {
|
|
|
|
notificationName = "xpinstall-disabled";
|
|
|
|
if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
|
2011-11-21 22:12:59 -08:00
|
|
|
message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
|
2011-10-26 13:21:51 -07:00
|
|
|
buttons = [];
|
|
|
|
} else {
|
2011-11-21 22:12:59 -08:00
|
|
|
message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
|
2011-10-26 13:21:51 -07:00
|
|
|
buttons = [{
|
|
|
|
label: strings.GetStringFromName("xpinstallDisabledButton"),
|
|
|
|
callback: function editPrefs() {
|
|
|
|
Services.prefs.setBoolPref("xpinstall.enabled", true);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
notificationName = "xpinstall";
|
2011-11-21 22:12:59 -08:00
|
|
|
message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
|
2011-10-26 13:21:51 -07:00
|
|
|
|
|
|
|
buttons = [{
|
|
|
|
label: strings.GetStringFromName("xpinstallPromptAllowButton"),
|
|
|
|
callback: function() {
|
|
|
|
// Kick off the install
|
|
|
|
installInfo.install();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2011-11-21 22:12:59 -08:00
|
|
|
NativeWindow.doorhanger.show(message, aTopic, buttons);
|
2011-10-26 13:21:51 -07:00
|
|
|
break;
|
|
|
|
}
|
2011-11-21 22:12:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
onInstallEnded: function(aInstall, aAddon) {
|
|
|
|
let needsRestart = false;
|
|
|
|
if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE))
|
|
|
|
needsRestart = true;
|
|
|
|
else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL)
|
|
|
|
needsRestart = true;
|
|
|
|
|
|
|
|
if (needsRestart) {
|
2012-01-15 08:50:03 -08:00
|
|
|
let buttons = [{
|
2011-11-21 22:12:59 -08:00
|
|
|
label: Strings.browser.GetStringFromName("notificationRestart.button"),
|
|
|
|
callback: function() {
|
|
|
|
// Notify all windows that an application quit has been requested
|
|
|
|
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
|
|
|
|
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
2011-11-29 09:42:43 -08:00
|
|
|
|
2011-11-21 22:12:59 -08:00
|
|
|
// If nothing aborted, quit the app
|
|
|
|
if (cancelQuit.data == false) {
|
|
|
|
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
|
|
|
|
appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
let message = Strings.browser.GetStringFromName("notificationRestart.normal");
|
|
|
|
NativeWindow.doorhanger.show(message, "addon-app-restart", buttons, BrowserApp.selectedTab.id, { persistence: -1 });
|
|
|
|
} else {
|
|
|
|
let message = Strings.browser.GetStringFromName("alertAddonsInstalledNoRestart");
|
|
|
|
NativeWindow.toast.show(message, "short");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onInstallFailed: function(aInstall) {
|
|
|
|
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsFail"), "short");
|
|
|
|
},
|
|
|
|
|
|
|
|
onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {},
|
|
|
|
|
|
|
|
onDownloadFailed: function(aInstall) {
|
|
|
|
this.onInstallFailed(aInstall);
|
|
|
|
},
|
|
|
|
|
2012-02-13 16:56:03 -08:00
|
|
|
onDownloadCancelled: function(aInstall) {
|
2012-02-14 15:32:45 -08:00
|
|
|
let host = (aInstall.originatingURI instanceof Ci.nsIStandardURL) && aInstall.originatingURI.host;
|
|
|
|
if (!host)
|
|
|
|
host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) && aInstall.sourceURI.host;
|
|
|
|
|
|
|
|
let error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError";
|
|
|
|
if (aInstall.error != 0)
|
|
|
|
error += aInstall.error;
|
|
|
|
else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
|
|
|
|
error += "Blocklisted";
|
|
|
|
else if (aInstall.addon && (!aInstall.addon.isCompatible || !aInstall.addon.isPlatformCompatible))
|
|
|
|
error += "Incompatible";
|
|
|
|
else
|
|
|
|
return; // No need to show anything in this case.
|
|
|
|
|
|
|
|
let msg = Strings.browser.GetStringFromName(error);
|
|
|
|
// TODO: formatStringFromName
|
|
|
|
msg = msg.replace("#1", aInstall.name);
|
|
|
|
if (host)
|
|
|
|
msg = msg.replace("#2", host);
|
|
|
|
msg = msg.replace("#3", Strings.brand.GetStringFromName("brandShortName"));
|
|
|
|
msg = msg.replace("#4", Services.appinfo.version);
|
2012-02-13 16:56:03 -08:00
|
|
|
|
|
|
|
NativeWindow.toast.show(msg, "short");
|
|
|
|
}
|
2011-10-26 13:21:51 -07:00
|
|
|
};
|
2011-11-03 23:54:47 -07:00
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
// 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;
|
|
|
|
|
|
|
|
var ViewportHandler = {
|
2012-01-06 16:42:44 -08:00
|
|
|
// The cached viewport metadata for each document. We tie viewport metadata to each document
|
|
|
|
// instead of to each tab so that we don't have to update it when the document changes. Using an
|
|
|
|
// ES6 weak map lets us avoid leaks.
|
|
|
|
_metadata: new WeakMap(),
|
|
|
|
|
2012-01-06 16:42:46 -08:00
|
|
|
// A list of document IDs, arbitrarily assigned. We use IDs to refer to content documents instead
|
|
|
|
// of strong references to avoid leaking them.
|
|
|
|
_documentIds: new WeakMap(),
|
|
|
|
_nextDocumentId: 0,
|
|
|
|
|
2011-11-29 09:35:26 -08:00
|
|
|
init: function init() {
|
|
|
|
addEventListener("DOMMetaAdded", this, false);
|
|
|
|
addEventListener("resize", this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function uninit() {
|
|
|
|
removeEventListener("DOMMetaAdded", this, false);
|
|
|
|
removeEventListener("resize", this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function handleEvent(aEvent) {
|
|
|
|
let target = aEvent.originalTarget;
|
|
|
|
let document = target.ownerDocument || target;
|
|
|
|
let browser = BrowserApp.getBrowserForDocument(document);
|
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
|
|
|
if (!tab)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (aEvent.type) {
|
|
|
|
case "DOMMetaAdded":
|
|
|
|
if (target.name == "viewport")
|
|
|
|
this.updateMetadata(tab);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "resize":
|
|
|
|
this.onResize();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
resetMetadata: function resetMetadata(tab) {
|
|
|
|
tab.updateViewportMetadata(null);
|
|
|
|
},
|
|
|
|
|
|
|
|
updateMetadata: function updateMetadata(tab) {
|
|
|
|
let metadata = this.getViewportMetadata(tab.browser.contentWindow);
|
|
|
|
tab.updateViewportMetadata(metadata);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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(aWindow) {
|
|
|
|
let doctype = aWindow.document.doctype;
|
|
|
|
if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))
|
|
|
|
return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
|
|
|
|
|
|
|
|
let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
|
|
|
|
if (handheldFriendly == "true")
|
|
|
|
return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
|
|
|
|
|
|
|
|
if (aWindow.document instanceof XULDocument)
|
|
|
|
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 = this.clamp(parseInt(widthStr), kViewportMinWidth, kViewportMaxWidth);
|
|
|
|
let height = this.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 = this.clamp(scale, kViewportMinScale, kViewportMaxScale);
|
|
|
|
minScale = this.clamp(minScale, kViewportMinScale, kViewportMaxScale);
|
|
|
|
maxScale = this.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
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
onResize: function onResize() {
|
|
|
|
for (let i = 0; i < BrowserApp.tabs.length; i++)
|
|
|
|
BrowserApp.tabs[i].updateViewportSize();
|
|
|
|
},
|
|
|
|
|
|
|
|
clamp: function(num, min, max) {
|
|
|
|
return Math.max(min, Math.min(max, num));
|
|
|
|
},
|
|
|
|
|
|
|
|
// The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
|
|
|
|
// This is higher on higher-dpi displays, so pages stay about the same physical size.
|
|
|
|
getScaleRatio: function getScaleRatio() {
|
|
|
|
let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio");
|
|
|
|
if (prefValue > 0)
|
|
|
|
return prefValue / 100;
|
|
|
|
|
|
|
|
let dpi = this.displayDPI;
|
|
|
|
if (dpi < 200) // Includes desktop displays, and LDPI and MDPI Android devices
|
|
|
|
return 1;
|
|
|
|
else if (dpi < 300) // Includes Nokia N900, and HDPI Android devices
|
|
|
|
return 1.5;
|
|
|
|
|
|
|
|
// For very high-density displays like the iPhone 4, calculate an integer ratio.
|
|
|
|
return Math.floor(dpi / 150);
|
|
|
|
},
|
|
|
|
|
|
|
|
get displayDPI() {
|
|
|
|
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
delete this.displayDPI;
|
|
|
|
return this.displayDPI = utils.displayDPI;
|
2012-01-06 16:42:44 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the viewport metadata for the given document, or the default metrics if no viewport
|
|
|
|
* metadata is available for that document.
|
|
|
|
*/
|
|
|
|
getMetadataForDocument: function getMetadataForDocument(aDocument) {
|
|
|
|
let metadata = this._metadata.get(aDocument, this.getDefaultMetadata());
|
|
|
|
return metadata;
|
|
|
|
},
|
|
|
|
|
|
|
|
/** Updates the saved viewport metadata for the given content document. */
|
|
|
|
setMetadataForDocument: function setMetadataForDocument(aDocument, aMetadata) {
|
|
|
|
if (!aMetadata)
|
|
|
|
this._metadata.delete(aDocument);
|
|
|
|
else
|
|
|
|
this._metadata.set(aDocument, aMetadata);
|
|
|
|
},
|
|
|
|
|
|
|
|
/** Returns the default viewport metadata for a document. */
|
|
|
|
getDefaultMetadata: function getDefaultMetadata() {
|
|
|
|
return {
|
|
|
|
autoSize: false,
|
|
|
|
allowZoom: true,
|
|
|
|
autoScale: true,
|
|
|
|
scaleRatio: ViewportHandler.getScaleRatio()
|
|
|
|
};
|
2012-01-06 16:42:46 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a globally unique ID for the given content document. Using IDs to refer to documents
|
|
|
|
* allows content documents to be identified without any possibility of leaking them.
|
|
|
|
*/
|
|
|
|
getIdForDocument: function getIdForDocument(aDocument) {
|
|
|
|
let id = this._documentIds.get(aDocument, null);
|
|
|
|
if (id == null) {
|
|
|
|
id = this._nextDocumentId++;
|
|
|
|
this._documentIds.set(aDocument, id);
|
|
|
|
}
|
|
|
|
return id;
|
2011-11-29 09:35:26 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-11-03 23:54:47 -07:00
|
|
|
/**
|
|
|
|
* Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
|
|
|
|
*/
|
|
|
|
var PopupBlockerObserver = {
|
|
|
|
onUpdatePageReport: function onUpdatePageReport(aEvent) {
|
|
|
|
let browser = BrowserApp.selectedBrowser;
|
|
|
|
if (aEvent.originalTarget != browser)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!browser.pageReport)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let result = Services.perms.testExactPermission(BrowserApp.selectedBrowser.currentURI, "popup");
|
|
|
|
if (result == Ci.nsIPermissionManager.DENY_ACTION)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only show the notification again if we've not already shown it. Since
|
|
|
|
// notifications are per-browser, we don't need to worry about re-adding
|
|
|
|
// it.
|
|
|
|
if (!browser.pageReport.reported) {
|
|
|
|
if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
|
|
|
|
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
|
|
|
|
let message;
|
|
|
|
let popupCount = browser.pageReport.length;
|
|
|
|
|
|
|
|
let strings = Strings.browser;
|
|
|
|
if (popupCount > 1)
|
|
|
|
message = strings.formatStringFromName("popupWarningMultiple", [brandShortName, popupCount], 2);
|
|
|
|
else
|
|
|
|
message = strings.formatStringFromName("popupWarning", [brandShortName], 1);
|
|
|
|
|
|
|
|
let buttons = [
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("popupButtonAllowOnce"),
|
|
|
|
callback: function() { PopupBlockerObserver.showPopupsForSite(); }
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("popupButtonAlwaysAllow2"),
|
|
|
|
callback: function() { PopupBlockerObserver.allowPopupsForSite(true); }
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("popupButtonNeverWarn2"),
|
|
|
|
callback: function() { PopupBlockerObserver.allowPopupsForSite(false); }
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
NativeWindow.doorhanger.show(message, "popup-blocked", buttons);
|
|
|
|
}
|
|
|
|
// Record the fact that we've reported this blocked popup, so we don't
|
|
|
|
// show it again.
|
|
|
|
browser.pageReport.reported = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
allowPopupsForSite: function allowPopupsForSite(aAllow) {
|
|
|
|
let currentURI = BrowserApp.selectedBrowser.currentURI;
|
|
|
|
Services.perms.add(currentURI, "popup", aAllow
|
|
|
|
? Ci.nsIPermissionManager.ALLOW_ACTION
|
|
|
|
: Ci.nsIPermissionManager.DENY_ACTION);
|
|
|
|
dump("Allowing popups for: " + currentURI);
|
|
|
|
},
|
|
|
|
|
|
|
|
showPopupsForSite: function showPopupsForSite() {
|
|
|
|
let uri = BrowserApp.selectedBrowser.currentURI;
|
|
|
|
let pageReport = BrowserApp.selectedBrowser.pageReport;
|
|
|
|
if (pageReport) {
|
|
|
|
for (let i = 0; i < pageReport.length; ++i) {
|
2011-11-29 09:35:26 -08:00
|
|
|
let popupURIspec = pageReport[i].popupWindowURI.spec;
|
2011-11-03 23:54:47 -07:00
|
|
|
|
|
|
|
// Sometimes the popup URI that we get back from the pageReport
|
|
|
|
// isn't useful (for instance, netscape.com's popup URI ends up
|
|
|
|
// being "http://www.netscape.com", which isn't really the URI of
|
|
|
|
// the popup they're trying to show). This isn't going to be
|
|
|
|
// useful to the user, so we won't create a menu item for it.
|
|
|
|
if (popupURIspec == "" || popupURIspec == "about:blank" || popupURIspec == uri.spec)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
let popupFeatures = pageReport[i].popupWindowFeatures;
|
|
|
|
let popupName = pageReport[i].popupWindowName;
|
|
|
|
|
|
|
|
BrowserApp.addTab(popupURIspec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2011-11-11 08:02:10 -08:00
|
|
|
|
|
|
|
|
|
|
|
var OfflineApps = {
|
|
|
|
init: function() {
|
|
|
|
BrowserApp.deck.addEventListener("MozApplicationManifest", this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function() {
|
|
|
|
BrowserApp.deck.removeEventListener("MozApplicationManifest", this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function(aEvent) {
|
|
|
|
if (aEvent.type == "MozApplicationManifest")
|
|
|
|
this.offlineAppRequested(aEvent.originalTarget.defaultView);
|
|
|
|
},
|
|
|
|
|
|
|
|
offlineAppRequested: function(aContentWindow) {
|
|
|
|
if (!Services.prefs.getBoolPref("browser.offline-apps.notify"))
|
|
|
|
return;
|
|
|
|
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(aContentWindow);
|
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
|
|
|
let currentURI = aContentWindow.document.documentURIObject;
|
|
|
|
|
|
|
|
// Don't bother showing UI if the user has already made a decision
|
|
|
|
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.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 strings = Strings.browser;
|
|
|
|
let buttons = [{
|
|
|
|
label: strings.GetStringFromName("offlineApps.allow"),
|
|
|
|
callback: function() {
|
|
|
|
OfflineApps.allowSite(aContentWindow.document);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("offlineApps.never"),
|
|
|
|
callback: function() {
|
|
|
|
OfflineApps.disallowSite(aContentWindow.document);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("offlineApps.notNow"),
|
|
|
|
callback: function() { /* noop */ }
|
|
|
|
}];
|
|
|
|
|
|
|
|
let message = strings.formatStringFromName("offlineApps.available2", [host], 1);
|
|
|
|
NativeWindow.doorhanger.show(message, notificationID, buttons, tab.id);
|
|
|
|
},
|
|
|
|
|
|
|
|
allowSite: function(aDocument) {
|
|
|
|
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
|
|
|
|
|
|
|
|
// When a site is enabled while loading, manifest resources will
|
|
|
|
// start fetching immediately. This one time we need to do it
|
|
|
|
// ourselves.
|
|
|
|
this._startFetching(aDocument);
|
|
|
|
},
|
|
|
|
|
|
|
|
disallowSite: function(aDocument) {
|
|
|
|
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
|
|
|
|
},
|
|
|
|
|
|
|
|
_startFetching: function(aDocument) {
|
|
|
|
if (!aDocument.documentElement)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let manifest = aDocument.documentElement.getAttribute("manifest");
|
|
|
|
if (!manifest)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let manifestURI = Services.io.newURI(manifest, aDocument.characterSet, aDocument.documentURIObject);
|
|
|
|
let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(Ci.nsIOfflineCacheUpdateService);
|
|
|
|
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window);
|
|
|
|
}
|
|
|
|
};
|
2011-11-15 14:45:10 -08:00
|
|
|
|
|
|
|
var IndexedDB = {
|
|
|
|
_permissionsPrompt: "indexedDB-permissions-prompt",
|
|
|
|
_permissionsResponse: "indexedDB-permissions-response",
|
|
|
|
|
|
|
|
_quotaPrompt: "indexedDB-quota-prompt",
|
|
|
|
_quotaResponse: "indexedDB-quota-response",
|
|
|
|
_quotaCancel: "indexedDB-quota-cancel",
|
|
|
|
|
|
|
|
init: function IndexedDB_init() {
|
|
|
|
Services.obs.addObserver(this, this._permissionsPrompt, false);
|
|
|
|
Services.obs.addObserver(this, this._quotaPrompt, false);
|
|
|
|
Services.obs.addObserver(this, this._quotaCancel, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function IndexedDB_uninit() {
|
|
|
|
Services.obs.removeObserver(this, this._permissionsPrompt, false);
|
|
|
|
Services.obs.removeObserver(this, this._quotaPrompt, false);
|
|
|
|
Services.obs.removeObserver(this, this._quotaCancel, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function IndexedDB_observe(subject, topic, data) {
|
|
|
|
if (topic != this._permissionsPrompt &&
|
|
|
|
topic != this._quotaPrompt &&
|
|
|
|
topic != this._quotaCancel) {
|
|
|
|
throw new Error("Unexpected topic!");
|
|
|
|
}
|
|
|
|
|
|
|
|
let requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
|
|
|
|
|
|
|
|
let contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
|
|
|
|
let contentDocument = contentWindow.document;
|
|
|
|
let browser = BrowserApp.getBrowserForWindow(contentWindow);
|
|
|
|
if (!browser)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let host = contentDocument.documentURIObject.asciiHost;
|
|
|
|
|
|
|
|
let strings = Strings.browser;
|
|
|
|
|
|
|
|
let message, responseTopic;
|
|
|
|
if (topic == this._permissionsPrompt) {
|
|
|
|
message = strings.formatStringFromName("offlineApps.available2", [host], 1);
|
|
|
|
responseTopic = this._permissionsResponse;
|
|
|
|
} else if (topic == this._quotaPrompt) {
|
|
|
|
message = strings.formatStringFromName("indexedDBQuota.wantsTo", [ host, data ], 2);
|
|
|
|
responseTopic = this._quotaResponse;
|
|
|
|
} else if (topic == this._quotaCancel) {
|
|
|
|
responseTopic = this._quotaResponse;
|
|
|
|
}
|
|
|
|
|
|
|
|
let notificationID = responseTopic + host;
|
2011-11-15 14:45:54 -08:00
|
|
|
let tab = BrowserApp.getTabForBrowser(browser);
|
2011-11-15 14:45:10 -08:00
|
|
|
let observer = requestor.getInterface(Ci.nsIObserver);
|
2011-11-15 14:45:54 -08:00
|
|
|
|
|
|
|
if (topic == this._quotaCancel) {
|
|
|
|
NativeWindow.doorhanger.hide(notificationID, tab.id);
|
|
|
|
observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-15 14:45:10 -08:00
|
|
|
let buttons = [{
|
|
|
|
label: strings.GetStringFromName("offlineApps.allow"),
|
|
|
|
callback: function() {
|
|
|
|
observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("offlineApps.never"),
|
|
|
|
callback: function() {
|
|
|
|
observer.observe(null, responseTopic, Ci.nsIPermissionManager.DENY_ACTION);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: strings.GetStringFromName("offlineApps.notNow"),
|
|
|
|
callback: function() {
|
|
|
|
observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
NativeWindow.doorhanger.show(message, notificationID, buttons, tab.id);
|
|
|
|
}
|
|
|
|
};
|
2011-11-30 14:37:59 -08:00
|
|
|
|
|
|
|
var ConsoleAPI = {
|
|
|
|
init: function init() {
|
|
|
|
Services.obs.addObserver(this, "console-api-log-event", false);
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function uninit() {
|
|
|
|
Services.obs.removeObserver(this, "console-api-log-event", false);
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function observe(aMessage, aTopic, aData) {
|
|
|
|
aMessage = aMessage.wrappedJSObject;
|
|
|
|
|
|
|
|
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://browser/locale/browser.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) {
|
|
|
|
let functionName = aFrame.functionName || bundle.GetStringFromName("stacktrace.anonymousFunction");
|
|
|
|
body += " " + aFrame.filename + " :: " + functionName + " :: " + aFrame.lineNumber + "\n";
|
|
|
|
});
|
|
|
|
|
|
|
|
Services.console.logStringMessage(body);
|
|
|
|
} else if (aMessage.level == "time" && aMessage.arguments) {
|
|
|
|
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
|
|
|
let body = bundle.formatStringFromName("timer.start", [aMessage.arguments.name], 1);
|
|
|
|
Services.console.logStringMessage(body);
|
|
|
|
} else if (aMessage.level == "timeEnd" && aMessage.arguments) {
|
|
|
|
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
|
|
|
let body = bundle.formatStringFromName("timer.end", [aMessage.arguments.name, aMessage.arguments.duration], 2);
|
|
|
|
Services.console.logStringMessage(body);
|
|
|
|
} else if (["group", "groupCollapsed", "groupEnd"].indexOf(aMessage.level) != -1) {
|
|
|
|
// Do nothing yet
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
};
|
2011-12-09 14:04:19 -08:00
|
|
|
|
2011-12-13 16:17:56 -08:00
|
|
|
var ClipboardHelper = {
|
|
|
|
init: function() {
|
|
|
|
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copy"), ClipboardHelper.getCopyContext(false), ClipboardHelper.copy.bind(ClipboardHelper));
|
|
|
|
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyAll"), ClipboardHelper.getCopyContext(true), ClipboardHelper.copy.bind(ClipboardHelper));
|
2012-01-11 13:59:39 -08:00
|
|
|
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.selectAll"), ClipboardHelper.selectAllContext, ClipboardHelper.select.bind(ClipboardHelper));
|
2011-12-13 16:17:56 -08:00
|
|
|
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.paste"), ClipboardHelper.pasteContext, ClipboardHelper.paste.bind(ClipboardHelper));
|
|
|
|
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.changeInputMethod"), NativeWindow.contextmenus.textContext, ClipboardHelper.inputMethod.bind(ClipboardHelper));
|
|
|
|
},
|
|
|
|
|
|
|
|
get clipboardHelper() {
|
|
|
|
delete this.clipboardHelper;
|
|
|
|
return this.clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
|
|
|
},
|
|
|
|
|
|
|
|
get clipboard() {
|
|
|
|
delete this.clipboard;
|
|
|
|
return this.clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
|
|
|
},
|
|
|
|
|
|
|
|
copy: function(aElement) {
|
|
|
|
let selectionStart = aElement.selectionStart;
|
|
|
|
let selectionEnd = aElement.selectionEnd;
|
|
|
|
if (selectionStart != selectionEnd) {
|
|
|
|
string = aElement.value.slice(selectionStart, selectionEnd);
|
|
|
|
this.clipboardHelper.copyString(string);
|
|
|
|
} else {
|
|
|
|
this.clipboardHelper.copyString(aElement.value);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
select: function(aElement) {
|
|
|
|
if (!aElement || !(aElement instanceof Ci.nsIDOMNSEditableElement))
|
|
|
|
return;
|
|
|
|
let target = aElement.QueryInterface(Ci.nsIDOMNSEditableElement);
|
|
|
|
target.editor.selectAll();
|
|
|
|
target.focus();
|
|
|
|
},
|
|
|
|
|
|
|
|
paste: function(aElement) {
|
|
|
|
if (!aElement || !(aElement instanceof Ci.nsIDOMNSEditableElement))
|
|
|
|
return;
|
|
|
|
let target = aElement.QueryInterface(Ci.nsIDOMNSEditableElement);
|
|
|
|
target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
|
|
|
|
target.focus();
|
|
|
|
},
|
|
|
|
|
|
|
|
inputMethod: function(aElement) {
|
|
|
|
Cc["@mozilla.org/imepicker;1"].getService(Ci.nsIIMEPicker).show();
|
|
|
|
},
|
|
|
|
|
|
|
|
getCopyContext: function(isCopyAll) {
|
|
|
|
return {
|
|
|
|
matches: function(aElement) {
|
|
|
|
if (NativeWindow.contextmenus.textContext.matches(aElement)) {
|
2012-01-11 13:59:39 -08:00
|
|
|
// Don't include "copy" for password fields.
|
|
|
|
// mozIsTextField(true) tests for only non-password fields.
|
|
|
|
if (aElement instanceof Ci.nsIDOMHTMLInputElement && !aElement.mozIsTextField(true))
|
|
|
|
return false;
|
|
|
|
|
2011-12-13 16:17:56 -08:00
|
|
|
let selectionStart = aElement.selectionStart;
|
|
|
|
let selectionEnd = aElement.selectionEnd;
|
|
|
|
if (selectionStart != selectionEnd)
|
|
|
|
return true;
|
2012-01-11 13:59:39 -08:00
|
|
|
|
|
|
|
if (isCopyAll && aElement.textLength > 0)
|
2011-12-13 16:17:56 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-01-11 13:59:39 -08:00
|
|
|
selectAllContext: {
|
|
|
|
matches: function selectAllContextMatches(aElement) {
|
|
|
|
if (NativeWindow.contextmenus.textContext.matches(aElement)) {
|
|
|
|
let selectionStart = aElement.selectionStart;
|
|
|
|
let selectionEnd = aElement.selectionEnd;
|
|
|
|
return (selectionStart > 0 || selectionEnd < aElement.textLength);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-12-13 16:17:56 -08:00
|
|
|
pasteContext: {
|
|
|
|
matches: function(aElement) {
|
|
|
|
if (NativeWindow.contextmenus.textContext.matches(aElement)) {
|
|
|
|
let flavors = ["text/unicode"];
|
|
|
|
return ClipboardHelper.clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2012-03-01 10:58:19 -08:00
|
|
|
};
|
2011-12-13 16:17:56 -08:00
|
|
|
|
2011-12-09 14:04:19 -08:00
|
|
|
var PluginHelper = {
|
|
|
|
showDoorHanger: function(aTab) {
|
2011-12-19 11:43:39 -08:00
|
|
|
let message = Strings.browser.GetStringFromName("clickToPlayPlugins.message");
|
2011-12-09 14:04:19 -08:00
|
|
|
let buttons = [
|
|
|
|
{
|
2011-12-19 11:43:39 -08:00
|
|
|
label: Strings.browser.GetStringFromName("clickToPlayPlugins.yes"),
|
2011-12-09 14:04:19 -08:00
|
|
|
callback: function() {
|
|
|
|
PluginHelper.playAllPlugins(aTab);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
2011-12-19 11:43:39 -08:00
|
|
|
label: Strings.browser.GetStringFromName("clickToPlayPlugins.no"),
|
2011-12-09 14:04:19 -08:00
|
|
|
callback: function() {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id);
|
|
|
|
},
|
|
|
|
|
2012-01-06 11:57:45 -08:00
|
|
|
playAllPlugins: function(aTab, aEvent) {
|
|
|
|
if (aEvent) {
|
|
|
|
if (!aEvent.isTrusted)
|
|
|
|
return;
|
|
|
|
aEvent.preventDefault();
|
|
|
|
}
|
|
|
|
|
2012-01-31 12:49:25 -08:00
|
|
|
this._findAndPlayAllPlugins(aTab.browser.contentWindow);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Helper function that recurses through sub-frames to find all plugin objects
|
|
|
|
_findAndPlayAllPlugins: function _findAndPlayAllPlugins(aWindow) {
|
|
|
|
let embeds = aWindow.document.getElementsByTagName("embed");
|
|
|
|
for (let i = 0; i < embeds.length; i++) {
|
|
|
|
if (!embeds[i].hasAttribute("played"))
|
|
|
|
this._playPlugin(embeds[i]);
|
2011-12-09 14:04:19 -08:00
|
|
|
}
|
2012-01-31 12:49:25 -08:00
|
|
|
|
|
|
|
let objects = aWindow.document.getElementsByTagName("object");
|
|
|
|
for (let i = 0; i < objects.length; i++) {
|
|
|
|
if (!objects[i].hasAttribute("played"))
|
|
|
|
this._playPlugin(objects[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < aWindow.frames.length; i++) {
|
|
|
|
this._findAndPlayAllPlugins(aWindow.frames[i]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_playPlugin: function _playPlugin(aPlugin) {
|
|
|
|
let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
|
|
|
objLoadingContent.playPlugin();
|
|
|
|
|
|
|
|
// Set an attribute on the plugin object to avoid re-loading it
|
|
|
|
aPlugin.setAttribute("played", true);
|
2011-12-09 14:04:19 -08:00
|
|
|
},
|
|
|
|
|
2011-12-13 14:28:45 -08:00
|
|
|
getPluginPreference: function getPluginPreference() {
|
|
|
|
let pluginDisable = Services.prefs.getBoolPref("plugin.disable");
|
|
|
|
if (pluginDisable)
|
|
|
|
return "0";
|
|
|
|
|
|
|
|
let clickToPlay = Services.prefs.getBoolPref("plugins.click_to_play");
|
|
|
|
return clickToPlay ? "2" : "1";
|
|
|
|
},
|
|
|
|
|
|
|
|
setPluginPreference: function setPluginPreference(aValue) {
|
|
|
|
switch (aValue) {
|
|
|
|
case "0": // Enable Plugins = No
|
|
|
|
Services.prefs.setBoolPref("plugin.disable", true);
|
|
|
|
Services.prefs.clearUserPref("plugins.click_to_play");
|
|
|
|
break;
|
|
|
|
case "1": // Enable Plugins = Yes
|
|
|
|
Services.prefs.clearUserPref("plugin.disable");
|
|
|
|
Services.prefs.setBoolPref("plugins.click_to_play", false);
|
|
|
|
break;
|
|
|
|
case "2": // Enable Plugins = Tap to Play (default)
|
|
|
|
Services.prefs.clearUserPref("plugin.disable");
|
|
|
|
Services.prefs.clearUserPref("plugins.click_to_play");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-12-09 14:04:19 -08:00
|
|
|
// Copied from /browser/base/content/browser.js
|
|
|
|
isTooSmall : function (plugin, overlay) {
|
|
|
|
// Is the <object>'s size too small to hold what we want to show?
|
|
|
|
let pluginRect = plugin.getBoundingClientRect();
|
|
|
|
// XXX bug 446693. The text-shadow on the submitted-report text at
|
|
|
|
// the bottom causes scrollHeight to be larger than it should be.
|
|
|
|
let overflows = (overlay.scrollWidth > pluginRect.width) ||
|
|
|
|
(overlay.scrollHeight - 5 > pluginRect.height);
|
|
|
|
|
|
|
|
return overflows;
|
|
|
|
}
|
|
|
|
};
|
2011-12-17 13:50:09 -08:00
|
|
|
|
|
|
|
var PermissionsHelper = {
|
|
|
|
|
2011-12-22 11:50:44 -08:00
|
|
|
_permissonTypes: ["password", "geolocation", "popup", "indexedDB",
|
2011-12-17 13:50:09 -08:00
|
|
|
"offline-app", "desktop-notification"],
|
|
|
|
_permissionStrings: {
|
|
|
|
"password": {
|
|
|
|
label: "password.rememberPassword",
|
|
|
|
allowed: "password.remember",
|
|
|
|
denied: "password.never"
|
|
|
|
},
|
2011-12-22 11:50:44 -08:00
|
|
|
"geolocation": {
|
2011-12-17 13:50:09 -08:00
|
|
|
label: "geolocation.shareLocation",
|
2011-12-22 11:50:44 -08:00
|
|
|
allowed: "geolocation.alwaysAllow",
|
|
|
|
denied: "geolocation.neverAllow"
|
2011-12-17 13:50:09 -08:00
|
|
|
},
|
|
|
|
"popup": {
|
|
|
|
label: "blockPopups.label",
|
|
|
|
allowed: "popupButtonAlwaysAllow2",
|
|
|
|
denied: "popupButtonNeverWarn2"
|
|
|
|
},
|
|
|
|
"indexedDB": {
|
|
|
|
label: "offlineApps.storeOfflineData",
|
|
|
|
allowed: "offlineApps.allow",
|
|
|
|
denied: "offlineApps.never"
|
|
|
|
},
|
|
|
|
"offline-app": {
|
|
|
|
label: "offlineApps.storeOfflineData",
|
|
|
|
allowed: "offlineApps.allow",
|
|
|
|
denied: "offlineApps.never"
|
|
|
|
},
|
|
|
|
"desktop-notification": {
|
|
|
|
label: "desktopNotification.useNotifications",
|
|
|
|
allowed: "desktopNotification.allow",
|
|
|
|
denied: "desktopNotification.dontAllow"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
init: function init() {
|
|
|
|
Services.obs.addObserver(this, "Permissions:Get", false);
|
|
|
|
Services.obs.addObserver(this, "Permissions:Clear", false);
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function observe(aSubject, aTopic, aData) {
|
|
|
|
let uri = BrowserApp.selectedBrowser.currentURI;
|
|
|
|
|
|
|
|
switch (aTopic) {
|
|
|
|
case "Permissions:Get":
|
|
|
|
let permissions = [];
|
|
|
|
for (let i = 0; i < this._permissonTypes.length; i++) {
|
|
|
|
let type = this._permissonTypes[i];
|
|
|
|
let value = this.getPermission(uri, type);
|
|
|
|
|
|
|
|
// Only add the permission if it was set by the user
|
|
|
|
if (value == Services.perms.UNKNOWN_ACTION)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Get the strings that correspond to the permission type
|
|
|
|
let typeStrings = this._permissionStrings[type];
|
|
|
|
let label = Strings.browser.GetStringFromName(typeStrings["label"]);
|
|
|
|
|
|
|
|
// Get the key to look up the appropriate string entity
|
|
|
|
let valueKey = value == Services.perms.ALLOW_ACTION ?
|
|
|
|
"allowed" : "denied";
|
|
|
|
let valueString = Strings.browser.GetStringFromName(typeStrings[valueKey]);
|
|
|
|
|
|
|
|
// If we implement a two-line UI, we will need to pass the label and
|
|
|
|
// value individually and let java handle the formatting
|
|
|
|
let setting = Strings.browser.formatStringFromName("siteSettings.labelToValue",
|
|
|
|
[ label, valueString ], 2)
|
|
|
|
permissions.push({
|
|
|
|
type: type,
|
|
|
|
setting: setting
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep track of permissions, so we know which ones to clear
|
|
|
|
this._currentPermissions = permissions;
|
|
|
|
|
2012-01-12 13:29:14 -08:00
|
|
|
let host;
|
|
|
|
try {
|
|
|
|
host = uri.host;
|
|
|
|
} catch(e) {
|
|
|
|
host = uri.spec;
|
|
|
|
}
|
2011-12-17 13:50:09 -08:00
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Permissions:Data",
|
2012-01-12 13:29:14 -08:00
|
|
|
host: host,
|
2011-12-17 13:50:09 -08:00
|
|
|
permissions: permissions
|
|
|
|
}
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Permissions:Clear":
|
|
|
|
// An array of the indices of the permissions we want to clear
|
|
|
|
let permissionsToClear = JSON.parse(aData);
|
|
|
|
|
|
|
|
for (let i = 0; i < permissionsToClear.length; i++) {
|
|
|
|
let indexToClear = permissionsToClear[i];
|
|
|
|
let permissionType = this._currentPermissions[indexToClear]["type"];
|
|
|
|
this.clearPermission(uri, permissionType);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the permission value stored for a specified permission type.
|
|
|
|
*
|
|
|
|
* @param aType
|
|
|
|
* The permission type string stored in permission manager.
|
2011-12-22 11:50:44 -08:00
|
|
|
* e.g. "geolocation", "indexedDB", "popup"
|
2011-12-17 13:50:09 -08:00
|
|
|
*
|
|
|
|
* @return A permission value defined in nsIPermissionManager.
|
|
|
|
*/
|
|
|
|
getPermission: function getPermission(aURI, aType) {
|
|
|
|
// Password saving isn't a nsIPermissionManager permission type, so handle
|
|
|
|
// it seperately.
|
|
|
|
if (aType == "password") {
|
|
|
|
// By default, login saving is enabled, so if it is disabled, the
|
|
|
|
// user selected the never remember option
|
|
|
|
if (!Services.logins.getLoginSavingEnabled(aURI.prePath))
|
|
|
|
return Services.perms.DENY_ACTION;
|
|
|
|
|
|
|
|
// Check to see if the user ever actually saved a login
|
|
|
|
if (Services.logins.countLogins(aURI.prePath, "", ""))
|
|
|
|
return Services.perms.ALLOW_ACTION;
|
|
|
|
|
|
|
|
return Services.perms.UNKNOWN_ACTION;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Geolocation consumers use testExactPermission
|
2011-12-22 11:50:44 -08:00
|
|
|
if (aType == "geolocation")
|
2011-12-17 13:50:09 -08:00
|
|
|
return Services.perms.testExactPermission(aURI, aType);
|
|
|
|
|
|
|
|
return Services.perms.testPermission(aURI, aType);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears a user-set permission value for the site given a permission type.
|
|
|
|
*
|
|
|
|
* @param aType
|
|
|
|
* The permission type string stored in permission manager.
|
2011-12-22 11:50:44 -08:00
|
|
|
* e.g. "geolocation", "indexedDB", "popup"
|
2011-12-17 13:50:09 -08:00
|
|
|
*/
|
|
|
|
clearPermission: function clearPermission(aURI, aType) {
|
|
|
|
// Password saving isn't a nsIPermissionManager permission type, so handle
|
|
|
|
// it seperately.
|
|
|
|
if (aType == "password") {
|
|
|
|
// Get rid of exisiting stored logings
|
|
|
|
let logins = Services.logins.findLogins({}, aURI.prePath, "", "");
|
|
|
|
for (let i = 0; i < logins.length; i++) {
|
|
|
|
Services.logins.removeLogin(logins[i]);
|
|
|
|
}
|
|
|
|
// Re-set login saving to enabled
|
|
|
|
Services.logins.setLoginSavingEnabled(aURI.prePath, true);
|
|
|
|
} else {
|
|
|
|
Services.perms.remove(aURI.host, aType);
|
2012-01-06 16:48:29 -08:00
|
|
|
// Clear content prefs set in ContentPermissionPrompt.js
|
|
|
|
Services.contentPrefs.removePref(aURI, aType + ".request.remember");
|
2011-12-17 13:50:09 -08:00
|
|
|
}
|
|
|
|
}
|
2012-01-23 13:47:48 -08:00
|
|
|
};
|
2011-12-21 13:08:01 -08:00
|
|
|
|
|
|
|
var MasterPassword = {
|
|
|
|
pref: "privacy.masterpassword.enabled",
|
2011-12-26 22:07:44 -08:00
|
|
|
_tokenName: "",
|
|
|
|
|
2011-12-21 13:08:01 -08:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
|
|
|
get enabled() {
|
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
|
|
|
setPassword: function setPassword(aPassword) {
|
|
|
|
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(aPassword);
|
|
|
|
else if (status == Ci.nsIPKCS11Slot.SLOT_READY)
|
|
|
|
token.changePassword("", aPassword);
|
|
|
|
|
|
|
|
this.updatePref();
|
|
|
|
return true;
|
|
|
|
} catch(e) {
|
|
|
|
dump("MasterPassword.setPassword: " + e);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
removePassword: function removePassword(aOldPassword) {
|
|
|
|
try {
|
|
|
|
let token = this._pk11DB.getInternalKeyToken();
|
|
|
|
if (token.checkPassword(aOldPassword)) {
|
|
|
|
token.changePassword(aOldPassword, "");
|
|
|
|
this.updatePref();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} catch(e) {
|
|
|
|
dump("MasterPassword.removePassword: " + e + "\n");
|
|
|
|
}
|
|
|
|
NativeWindow.toast.show(Strings.browser.GetStringFromName("masterPassword.incorrect"), "short");
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
updatePref: function() {
|
|
|
|
var prefs = [];
|
|
|
|
let pref = {
|
|
|
|
name: this.pref,
|
|
|
|
type: "bool",
|
|
|
|
value: this.enabled
|
|
|
|
};
|
|
|
|
prefs.push(pref);
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "Preferences:Data",
|
|
|
|
preferences: prefs
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2012-01-23 13:47:48 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
var CharacterEncoding = {
|
|
|
|
_charsets: [],
|
|
|
|
|
|
|
|
init: function init() {
|
|
|
|
Services.obs.addObserver(this, "CharEncoding:Get", false);
|
|
|
|
Services.obs.addObserver(this, "CharEncoding:Set", false);
|
|
|
|
this.sendState();
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function uninit() {
|
|
|
|
Services.obs.removeObserver(this, "CharEncoding:Get", false);
|
|
|
|
Services.obs.removeObserver(this, "CharEncoding:Set", false);
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function observe(aSubject, aTopic, aData) {
|
|
|
|
switch (aTopic) {
|
|
|
|
case "CharEncoding:Get":
|
|
|
|
this.getEncoding();
|
|
|
|
break;
|
|
|
|
case "CharEncoding:Set":
|
|
|
|
this.setEncoding(aData);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
sendState: function sendState() {
|
|
|
|
let showCharEncoding = "false";
|
|
|
|
try {
|
|
|
|
showCharEncoding = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data;
|
|
|
|
} catch (e) { /* Optional */ }
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "CharEncoding:State",
|
|
|
|
visible: showCharEncoding
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getEncoding: function getEncoding() {
|
|
|
|
function normalizeCharsetCode(charsetCode) {
|
|
|
|
return charsetCode.trim().toLowerCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTitle(charsetCode) {
|
|
|
|
let charsetTitle = charsetCode;
|
|
|
|
try {
|
|
|
|
charsetTitle = Strings.charset.GetStringFromName(charsetCode + ".title");
|
|
|
|
} catch (e) {
|
|
|
|
dump("error: title not found for " + charsetCode);
|
|
|
|
}
|
|
|
|
return charsetTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._charsets.length) {
|
|
|
|
let charsets = Services.prefs.getComplexValue("intl.charsetmenu.browser.static", Ci.nsIPrefLocalizedString).data;
|
|
|
|
this._charsets = charsets.split(",").map(function (charset) {
|
|
|
|
return {
|
|
|
|
code: normalizeCharsetCode(charset),
|
|
|
|
title: getTitle(charset)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// if document charset is not in charset options, add it
|
|
|
|
let docCharset = normalizeCharsetCode(BrowserApp.selectedBrowser.contentDocument.characterSet);
|
|
|
|
let selected = 0;
|
|
|
|
let charsetCount = this._charsets.length;
|
|
|
|
for (; selected < charsetCount && this._charsets[selected].code != docCharset; selected++);
|
|
|
|
if (selected == charsetCount) {
|
|
|
|
this._charsets.push({
|
|
|
|
code: docCharset,
|
|
|
|
title: getTitle(docCharset)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
gecko: {
|
|
|
|
type: "CharEncoding:Data",
|
|
|
|
charsets: this._charsets,
|
|
|
|
selected: selected
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
setEncoding: function setEncoding(aEncoding) {
|
|
|
|
let browser = BrowserApp.selectedBrowser;
|
|
|
|
let docCharset = browser.docShell.QueryInterface(Ci.nsIDocCharset);
|
|
|
|
docCharset.charset = aEncoding;
|
|
|
|
browser.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
|
2012-02-10 06:44:06 -08:00
|
|
|
}
|
2012-01-23 13:47:48 -08:00
|
|
|
};
|
|
|
|
|