Merge m-c to m-i
@ -401,8 +401,6 @@
|
||||
@RESPATH@/components/SiteSpecificUserAgent.manifest
|
||||
@RESPATH@/components/storage-json.js
|
||||
@RESPATH@/components/crypto-SDR.js
|
||||
@RESPATH@/components/jsconsole-clhandler.manifest
|
||||
@RESPATH@/components/jsconsole-clhandler.js
|
||||
@RESPATH@/components/Downloads.manifest
|
||||
@RESPATH@/components/DownloadLegacy.js
|
||||
@RESPATH@/components/nsSidebar.manifest
|
||||
|
@ -1432,3 +1432,5 @@ pref("browser.migration.automigrate", false);
|
||||
pref("dom.mozBrowserFramesEnabled", true);
|
||||
|
||||
pref("extensions.pocket.enabled", true);
|
||||
|
||||
pref("signon.schemeUpgrades", true);
|
||||
|
@ -3911,11 +3911,6 @@ function addToUrlbarHistory(aUrlToAdd) {
|
||||
PlacesUIUtils.markPageAsTyped(aUrlToAdd);
|
||||
}
|
||||
|
||||
function toJavaScriptConsole()
|
||||
{
|
||||
toOpenWindowByType("global:console", "chrome://global/content/console.xul");
|
||||
}
|
||||
|
||||
function BrowserDownloadsUI()
|
||||
{
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
@ -7242,8 +7237,13 @@ var gIdentityHandler = {
|
||||
label.setAttribute("control", menulist.getAttribute("id"));
|
||||
label.textContent = aPermission.label;
|
||||
|
||||
let img = document.createElement("image");
|
||||
img.setAttribute("class",
|
||||
"identity-popup-permission-icon " + aPermission.id + "-icon");
|
||||
|
||||
let container = document.createElement("hbox");
|
||||
container.setAttribute("align", "center");
|
||||
container.appendChild(img);
|
||||
container.appendChild(label);
|
||||
container.appendChild(menulist);
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
|
||||
|
||||
<overlay id="jsConsoleOverlay"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<window id="JSConsoleWindow">
|
||||
|
||||
#include browserMountPoints.inc
|
||||
|
||||
</window>
|
||||
|
||||
</overlay>
|
@ -216,8 +216,6 @@ skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notific
|
||||
skip-if = true # bug 1057615
|
||||
[browser_bug563588.js]
|
||||
[browser_bug565575.js]
|
||||
[browser_bug565667.js]
|
||||
skip-if = toolkit != "cocoa"
|
||||
[browser_bug567306.js]
|
||||
subsuite = clipboard
|
||||
[browser_bug575561.js]
|
||||
|
@ -1,59 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var fm = Services.focus;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
// Open the javascript console. It has the mac menu overlay, so browser.js is
|
||||
// loaded in it.
|
||||
let consoleWin = window.open("chrome://global/content/console.xul", "_blank",
|
||||
"chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
|
||||
testWithOpenWindow(consoleWin);
|
||||
}
|
||||
|
||||
function testWithOpenWindow(consoleWin) {
|
||||
// Add a tab so we don't open the url into the current tab
|
||||
let newTab = gBrowser.addTab("http://example.com");
|
||||
gBrowser.selectedTab = newTab;
|
||||
|
||||
let numTabs = gBrowser.tabs.length;
|
||||
|
||||
waitForFocus(function() {
|
||||
// Sanity check
|
||||
is(fm.activeWindow, consoleWin,
|
||||
"the console window is focused");
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", function(aEvent) {
|
||||
gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, true);
|
||||
let browser = aEvent.originalTarget.linkedBrowser;
|
||||
browser.addEventListener("pageshow", function(event) {
|
||||
if (event.target.location.href != "about:addons")
|
||||
return;
|
||||
browser.removeEventListener("pageshow", arguments.callee, true);
|
||||
|
||||
is(fm.activeWindow, window,
|
||||
"the browser window was focused");
|
||||
is(browser.currentURI.spec, "about:addons",
|
||||
"about:addons was loaded in the window");
|
||||
is(gBrowser.tabs.length, numTabs + 1,
|
||||
"a new tab was added");
|
||||
|
||||
// Cleanup.
|
||||
executeSoon(function() {
|
||||
consoleWin.close();
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.removeTab(newTab);
|
||||
finish();
|
||||
});
|
||||
}, true);
|
||||
}, true);
|
||||
|
||||
// Open the addons manager, uses switchToTabHavingURI.
|
||||
consoleWin.BrowserOpenAddonsMgr();
|
||||
}, consoleWin);
|
||||
}
|
||||
|
||||
// Ideally we'd also check that the case for no open windows works, but we can't
|
||||
// due to limitations with the testing framework.
|
@ -45,6 +45,10 @@ add_task(function* testMainViewVisible() {
|
||||
is(menulists[0].value, "1", "Correct value on install menulist");
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
|
||||
let img = menulists[0].parentNode.querySelector("image");
|
||||
ok(img, "There is an image for the permissions");
|
||||
ok(img.classList.contains("install-icon"), "proper class is in image class");
|
||||
|
||||
gIdentityHandler.setPermission("install", SitePermissions.getDefault("install"));
|
||||
|
||||
gIdentityHandler._identityBox.click();
|
||||
|
@ -5,7 +5,6 @@ browser.jar:
|
||||
% content browser %content/browser/ contentaccessible=yes
|
||||
#ifdef XP_MACOSX
|
||||
% overlay chrome://mozapps/content/downloads/downloads.xul chrome://browser/content/downloadManagerOverlay.xul
|
||||
% overlay chrome://global/content/console.xul chrome://browser/content/jsConsoleOverlay.xul
|
||||
% overlay chrome://mozapps/content/update/updates.xul chrome://browser/content/softwareUpdateOverlay.xul
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
@ -180,7 +179,6 @@ browser.jar:
|
||||
#ifdef XP_MACOSX
|
||||
* content/browser/macBrowserOverlay.xul (content/macBrowserOverlay.xul)
|
||||
* content/browser/downloadManagerOverlay.xul (content/downloadManagerOverlay.xul)
|
||||
* content/browser/jsConsoleOverlay.xul (content/jsConsoleOverlay.xul)
|
||||
* content/browser/softwareUpdateOverlay.xul (content/softwareUpdateOverlay.xul)
|
||||
#endif
|
||||
* content/browser/viewSourceOverlay.xul (content/viewSourceOverlay.xul)
|
||||
|
@ -87,7 +87,8 @@
|
||||
<!-- Permissions Section -->
|
||||
<hbox class="identity-popup-section">
|
||||
<vbox id="identity-popup-permissions-content" flex="1">
|
||||
<label class="identity-popup-headline"
|
||||
<label id="identity-popup-permissions-headline"
|
||||
class="identity-popup-headline"
|
||||
value="&identity.permissions;"/>
|
||||
<vbox id="identity-popup-permission-list"/>
|
||||
<description>&identity.permissionsEmpty;</description>
|
||||
|
@ -302,9 +302,8 @@ HistoryDownloadElementShell.prototype = {
|
||||
|
||||
if (this.element.selected) {
|
||||
goUpdateDownloadCommands();
|
||||
} else {
|
||||
goUpdateCommand("downloadsCmd_clearDownloads");
|
||||
}
|
||||
goUpdateCommand("downloadsCmd_clearDownloads");
|
||||
},
|
||||
|
||||
onChanged() {
|
||||
|
@ -15,12 +15,13 @@ function test() {
|
||||
let win = OpenBrowserWindow({private: true});
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
let consoleWin = win.open("chrome://global/content/console.xul", "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
|
||||
consoleWin.addEventListener("load", function consoleLoad() {
|
||||
consoleWin.removeEventListener("load", consoleLoad, false);
|
||||
let chromeWin = win.open("chrome://browser/content/places/places.xul",
|
||||
"_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
|
||||
chromeWin.addEventListener("load", function chromeWinLoad() {
|
||||
chromeWin.removeEventListener("load", chromeWinLoad, false);
|
||||
win.close();
|
||||
}, false);
|
||||
windowsToClose.push(consoleWin);
|
||||
windowsToClose.push(chromeWin);
|
||||
}, false);
|
||||
|
||||
let observer = function() {
|
||||
|
@ -1,3 +1,3 @@
|
||||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.5.281
|
||||
Current extension version is: 1.5.305
|
||||
|
@ -65,22 +65,15 @@ function getFindBar(domWindow) {
|
||||
if (PdfjsContentUtils.isRemote) {
|
||||
throw new Error('FindBar is not accessible from the content process.');
|
||||
}
|
||||
var browser = getContainingBrowser(domWindow);
|
||||
try {
|
||||
var browser = getContainingBrowser(domWindow);
|
||||
var tabbrowser = browser.getTabBrowser();
|
||||
var tab;
|
||||
tab = tabbrowser.getTabForBrowser(browser);
|
||||
var tab = tabbrowser.getTabForBrowser(browser);
|
||||
return tabbrowser.getFindBar(tab);
|
||||
} catch (e) {
|
||||
try {
|
||||
// FF22 has no _getTabForBrowser, and FF24 has no getFindBar
|
||||
var chromeWindow = browser.ownerDocument.defaultView;
|
||||
return chromeWindow.gFindBar;
|
||||
} catch (ex) {
|
||||
// Suppress errors for PDF files opened in the bookmark sidebar, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1248959.
|
||||
return null;
|
||||
}
|
||||
// Suppress errors for PDF files opened in the bookmark sidebar, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1248959.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,18 +148,17 @@ function getLocalizedString(strings, id, property) {
|
||||
return id;
|
||||
}
|
||||
|
||||
function makeContentReadable(obj, window) {
|
||||
/* jshint -W027 */
|
||||
return Cu.cloneInto(obj, window);
|
||||
}
|
||||
|
||||
function createNewChannel(uri) {
|
||||
function createNewChannel(uri, node) {
|
||||
return NetUtil.newChannel({
|
||||
uri: uri,
|
||||
loadUsingSystemPrincipal: true
|
||||
loadUsingSystemPrincipal: true,
|
||||
});
|
||||
}
|
||||
|
||||
function asyncOpenChannel(channel, listener, context) {
|
||||
return channel.asyncOpen2(listener);
|
||||
}
|
||||
|
||||
function asyncFetchChannel(channel, callback) {
|
||||
return NetUtil.asyncFetch(channel, callback);
|
||||
}
|
||||
@ -264,7 +256,7 @@ ChromeActions.prototype = {
|
||||
getService(Ci.nsIExternalHelperAppService);
|
||||
|
||||
var docIsPrivate = this.isInPrivateBrowsing();
|
||||
var netChannel = createNewChannel(blobUri);
|
||||
var netChannel = createNewChannel(blobUri, this.domWindow.document);
|
||||
if ('nsIPrivateBrowsingChannel' in Ci &&
|
||||
netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
|
||||
netChannel.setPrivate(docIsPrivate);
|
||||
@ -327,7 +319,7 @@ ChromeActions.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
channel.asyncOpen2(listener);
|
||||
asyncOpenChannel(channel, listener, null);
|
||||
});
|
||||
},
|
||||
getLocale: function() {
|
||||
@ -367,11 +359,7 @@ ChromeActions.prototype = {
|
||||
return (!!prefBrowser && prefGfx);
|
||||
},
|
||||
supportsDocumentColors: function() {
|
||||
if (getIntPref('browser.display.document_color_use', 0) === 2 ||
|
||||
!getBoolPref('browser.display.use_document_colors', true)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return getIntPref('browser.display.document_color_use', 0) !== 2;
|
||||
},
|
||||
supportedMouseWheelZoomModifierKeys: function() {
|
||||
return {
|
||||
@ -783,7 +771,7 @@ RequestListener.prototype.receive = function(event) {
|
||||
var response;
|
||||
if (sync) {
|
||||
response = actions[action].call(this.actions, data);
|
||||
event.detail.response = makeContentReadable(response, doc.defaultView);
|
||||
event.detail.response = Cu.cloneInto(response, doc.defaultView);
|
||||
} else {
|
||||
if (!event.detail.responseExpected) {
|
||||
doc.documentElement.removeChild(message);
|
||||
@ -792,8 +780,7 @@ RequestListener.prototype.receive = function(event) {
|
||||
response = function sendResponse(response) {
|
||||
try {
|
||||
var listener = doc.createEvent('CustomEvent');
|
||||
let detail = makeContentReadable({response: response},
|
||||
doc.defaultView);
|
||||
let detail = Cu.cloneInto({ response: response }, doc.defaultView);
|
||||
listener.initCustomEvent('pdf.js.response', true, false, detail);
|
||||
return message.dispatchEvent(listener);
|
||||
} catch (e) {
|
||||
@ -837,7 +824,7 @@ FindEventManager.prototype.receiveMessage = function(msg) {
|
||||
var type = msg.data.type;
|
||||
var contentWindow = this.contentWindow;
|
||||
|
||||
detail = makeContentReadable(detail, contentWindow);
|
||||
detail = Cu.cloneInto(detail, contentWindow);
|
||||
var forward = contentWindow.document.createEvent('CustomEvent');
|
||||
forward.initCustomEvent(type, true, true, detail);
|
||||
contentWindow.dispatchEvent(forward);
|
||||
@ -972,7 +959,7 @@ PdfStreamConverter.prototype = {
|
||||
.createInstance(Ci.nsIBinaryInputStream);
|
||||
|
||||
// Create a new channel that is viewer loaded as a resource.
|
||||
var channel = createNewChannel(PDF_VIEWER_WEB_PAGE);
|
||||
var channel = createNewChannel(PDF_VIEWER_WEB_PAGE, null);
|
||||
|
||||
var listener = this.listener;
|
||||
var dataListener = this.dataListener;
|
||||
@ -1024,18 +1011,17 @@ PdfStreamConverter.prototype = {
|
||||
channel.loadGroup = aRequest.loadGroup;
|
||||
channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
|
||||
|
||||
// We can use resource principal when data is fetched by the chrome
|
||||
// make sure we reuse the origin attributes from the request channel to keep
|
||||
// isolation consistent.
|
||||
// e.g. useful for NoScript
|
||||
// We can use the resource principal when data is fetched by the chrome,
|
||||
// e.g. useful for NoScript. Make make sure we reuse the origin attributes
|
||||
// from the request channel to keep isolation consistent.
|
||||
var ssm = Cc['@mozilla.org/scriptsecuritymanager;1']
|
||||
.getService(Ci.nsIScriptSecurityManager);
|
||||
var uri = NetUtil.newURI(PDF_VIEWER_WEB_PAGE, null, null);
|
||||
var attrs = aRequest.loadInfo.originAttributes;
|
||||
var resourcePrincipal;
|
||||
resourcePrincipal = ssm.createCodebasePrincipal(uri, attrs);
|
||||
resourcePrincipal =
|
||||
ssm.createCodebasePrincipal(uri, aRequest.loadInfo.originAttributes);
|
||||
aRequest.owner = resourcePrincipal;
|
||||
channel.asyncOpen2(proxy);
|
||||
asyncOpenChannel(channel, proxy, aContext);
|
||||
},
|
||||
|
||||
// nsIRequestObserver::onStopRequest
|
||||
|
@ -181,8 +181,7 @@ var PdfjsChromeUtils = {
|
||||
_findbarFromMessage: function(aMsg) {
|
||||
let browser = aMsg.target;
|
||||
let tabbrowser = browser.getTabBrowser();
|
||||
let tab;
|
||||
tab = tabbrowser.getTabForBrowser(browser);
|
||||
let tab = tabbrowser.getTabForBrowser(browser);
|
||||
return tabbrowser.getFindBar(tab);
|
||||
},
|
||||
|
||||
|
@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdf = {}));
|
||||
// Use strict in our context only - users might not want it
|
||||
'use strict';
|
||||
|
||||
var pdfjsVersion = '1.5.281';
|
||||
var pdfjsBuild = '5a5bb99';
|
||||
var pdfjsVersion = '1.5.305';
|
||||
var pdfjsBuild = '546e223';
|
||||
|
||||
var pdfjsFilePath =
|
||||
typeof document !== 'undefined' && document.currentScript ?
|
||||
@ -1070,6 +1070,11 @@ function isArrayBuffer(v) {
|
||||
return typeof v === 'object' && v !== null && v.byteLength !== undefined;
|
||||
}
|
||||
|
||||
// Checks if ch is one of the following characters: SPACE, TAB, CR or LF.
|
||||
function isSpace(ch) {
|
||||
return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise Capability object.
|
||||
*
|
||||
@ -1441,6 +1446,7 @@ exports.isEmptyObj = isEmptyObj;
|
||||
exports.isInt = isInt;
|
||||
exports.isNum = isNum;
|
||||
exports.isString = isString;
|
||||
exports.isSpace = isSpace;
|
||||
exports.isSameOrigin = isSameOrigin;
|
||||
exports.isValidUrl = isValidUrl;
|
||||
exports.isLittleEndian = isLittleEndian;
|
||||
@ -7815,7 +7821,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
|
||||
},
|
||||
|
||||
getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
|
||||
return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref });
|
||||
return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref }).
|
||||
then(function (pageIndex) {
|
||||
return pageIndex;
|
||||
}, function (reason) {
|
||||
return Promise.reject(new Error(reason));
|
||||
});
|
||||
},
|
||||
|
||||
getAnnotations: function WorkerTransport_getAnnotations(pageIndex, intent) {
|
||||
|
1931
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
@ -18,6 +18,7 @@
|
||||
|
||||
var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
|
||||
|
||||
|
||||
var pdfjsWebLibs = {
|
||||
pdfjsWebPDFJS: window.pdfjsDistBuildPdf
|
||||
};
|
||||
@ -4590,6 +4591,11 @@ exports.PDFFindBar = PDFFindBar;
|
||||
|
||||
var parseQueryString = uiUtils.parseQueryString;
|
||||
|
||||
var PageNumberRegExp = /^\d+$/;
|
||||
function isPageNumber(str) {
|
||||
return PageNumberRegExp.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFLinkServiceOptions
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
@ -4601,7 +4607,7 @@ var parseQueryString = uiUtils.parseQueryString;
|
||||
* @class
|
||||
* @implements {IPDFLinkService}
|
||||
*/
|
||||
var PDFLinkService = (function () {
|
||||
var PDFLinkService = (function PDFLinkServiceClosure() {
|
||||
/**
|
||||
* @constructs PDFLinkService
|
||||
* @param {PDFLinkServiceOptions} options
|
||||
@ -4661,7 +4667,7 @@ var PDFLinkService = (function () {
|
||||
var self = this;
|
||||
|
||||
var goToDestination = function(destRef) {
|
||||
// dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
|
||||
// dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
|
||||
var pageNumber = destRef instanceof Object ?
|
||||
self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
|
||||
(destRef + 1);
|
||||
@ -4711,30 +4717,15 @@ var PDFLinkService = (function () {
|
||||
*/
|
||||
getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
|
||||
if (typeof dest === 'string') {
|
||||
return this.getAnchorUrl('#' + escape(dest));
|
||||
// In practice, a named destination may contain only a number.
|
||||
// If that happens, use the '#nameddest=' form to avoid the link
|
||||
// redirecting to a page, instead of the correct destination.
|
||||
return this.getAnchorUrl(
|
||||
'#' + (isPageNumber(dest) ? 'nameddest=' : '') + escape(dest));
|
||||
}
|
||||
if (dest instanceof Array) {
|
||||
var destRef = dest[0]; // see navigateTo method for dest format
|
||||
var pageNumber = destRef instanceof Object ?
|
||||
this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
|
||||
(destRef + 1);
|
||||
if (pageNumber) {
|
||||
var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
|
||||
var destKind = dest[1];
|
||||
if (typeof destKind === 'object' && 'name' in destKind &&
|
||||
destKind.name === 'XYZ') {
|
||||
var scale = (dest[4] || this.pdfViewer.currentScaleValue);
|
||||
var scaleNumber = parseFloat(scale);
|
||||
if (scaleNumber) {
|
||||
scale = scaleNumber * 100;
|
||||
}
|
||||
pdfOpenParams += '&zoom=' + scale;
|
||||
if (dest[2] || dest[3]) {
|
||||
pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
|
||||
}
|
||||
}
|
||||
return pdfOpenParams;
|
||||
}
|
||||
var str = JSON.stringify(dest);
|
||||
return this.getAnchorUrl('#' + escape(str));
|
||||
}
|
||||
return this.getAnchorUrl('');
|
||||
},
|
||||
@ -4753,6 +4744,7 @@ var PDFLinkService = (function () {
|
||||
* @param {string} hash
|
||||
*/
|
||||
setHash: function PDFLinkService_setHash(hash) {
|
||||
var pageNumber, dest;
|
||||
if (hash.indexOf('=') >= 0) {
|
||||
var params = parseQueryString(hash);
|
||||
if ('search' in params) {
|
||||
@ -4770,7 +4762,6 @@ var PDFLinkService = (function () {
|
||||
this.navigateTo(params.nameddest);
|
||||
return;
|
||||
}
|
||||
var pageNumber, dest;
|
||||
if ('page' in params) {
|
||||
pageNumber = (params.page | 0) || 1;
|
||||
}
|
||||
@ -4820,13 +4811,23 @@ var PDFLinkService = (function () {
|
||||
mode: params.pagemode
|
||||
});
|
||||
}
|
||||
} else if (/^\d+$/.test(hash)) { // page number
|
||||
this.page = hash;
|
||||
} else { // named destination
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.updateNextHashParam(unescape(hash));
|
||||
} else if (isPageNumber(hash)) { // Page number.
|
||||
this.page = hash | 0;
|
||||
} else { // Named (or explicit) destination.
|
||||
dest = unescape(hash);
|
||||
try {
|
||||
dest = JSON.parse(dest);
|
||||
} catch (ex) {}
|
||||
|
||||
if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.updateNextHashParam(dest);
|
||||
}
|
||||
this.navigateTo(dest);
|
||||
return;
|
||||
}
|
||||
this.navigateTo(unescape(hash));
|
||||
console.error('PDFLinkService_setHash: \'' + unescape(hash) +
|
||||
'\' is not a valid destination.');
|
||||
}
|
||||
},
|
||||
|
||||
@ -4884,6 +4885,60 @@ var PDFLinkService = (function () {
|
||||
}
|
||||
};
|
||||
|
||||
function isValidExplicitDestination(dest) {
|
||||
if (!(dest instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
var destLength = dest.length, allowNull = true;
|
||||
if (destLength < 2) {
|
||||
return false;
|
||||
}
|
||||
var page = dest[0];
|
||||
if (!(typeof page === 'object' &&
|
||||
typeof page.num === 'number' && (page.num | 0) === page.num &&
|
||||
typeof page.gen === 'number' && (page.gen | 0) === page.gen) &&
|
||||
!(typeof page === 'number' && (page | 0) === page && page >= 0)) {
|
||||
return false;
|
||||
}
|
||||
var zoom = dest[1];
|
||||
if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
|
||||
return false;
|
||||
}
|
||||
switch (zoom.name) {
|
||||
case 'XYZ':
|
||||
if (destLength !== 5) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'Fit':
|
||||
case 'FitB':
|
||||
return destLength === 2;
|
||||
case 'FitH':
|
||||
case 'FitBH':
|
||||
case 'FitV':
|
||||
case 'FitBV':
|
||||
if (destLength !== 3) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'FitR':
|
||||
if (destLength !== 6) {
|
||||
return false;
|
||||
}
|
||||
allowNull = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
for (var i = 2; i < destLength; i++) {
|
||||
var param = dest[i];
|
||||
if (!(typeof param === 'number' || (allowNull && param === null))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return PDFLinkService;
|
||||
})();
|
||||
|
||||
@ -6723,6 +6778,8 @@ var PDFViewer = (function pdfViewer() {
|
||||
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
|
||||
break;
|
||||
default:
|
||||
console.error('PDFViewer_scrollPageIntoView: \'' + dest[1].name +
|
||||
'\' is not a valid destination type.');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -7692,6 +7749,7 @@ var PDFViewerApplication = {
|
||||
pdfViewer.setDocument(pdfDocument);
|
||||
var firstPagePromise = pdfViewer.firstPagePromise;
|
||||
var pagesPromise = pdfViewer.pagesPromise;
|
||||
var onePageRendered = pdfViewer.onePageRendered;
|
||||
|
||||
this.pageRotation = 0;
|
||||
|
||||
@ -7797,9 +7855,8 @@ var PDFViewerApplication = {
|
||||
}
|
||||
});
|
||||
|
||||
// outline depends on pagesRefMap
|
||||
var promises = [pagesPromise, this.animationStartedPromise];
|
||||
Promise.all(promises).then(function() {
|
||||
Promise.all([onePageRendered, this.animationStartedPromise]).then(
|
||||
function() {
|
||||
pdfDocument.getOutline().then(function(outline) {
|
||||
self.pdfOutlineViewer.render({ outline: outline });
|
||||
});
|
||||
@ -9226,7 +9283,6 @@ exports.FirefoxCom = FirefoxCom;
|
||||
// FIXME the l10n.js file in the Firefox extension needs global FirefoxCom.
|
||||
window.FirefoxCom = pdfjsWebLibs.pdfjsWebFirefoxCom.FirefoxCom;
|
||||
|
||||
|
||||
function getViewerConfiguration() {
|
||||
return {
|
||||
appContainer: document.body,
|
||||
|
@ -392,8 +392,6 @@
|
||||
@RESPATH@/components/crypto-SDR.js
|
||||
@RESPATH@/components/TooltipTextProvider.js
|
||||
@RESPATH@/components/TooltipTextProvider.manifest
|
||||
@RESPATH@/components/jsconsole-clhandler.manifest
|
||||
@RESPATH@/components/jsconsole-clhandler.js
|
||||
@RESPATH@/components/webvtt.xpt
|
||||
@RESPATH@/components/WebVTT.manifest
|
||||
@RESPATH@/components/WebVTTParserWrapper.js
|
||||
|
@ -253,4 +253,3 @@ var gPermissionObject = {
|
||||
};
|
||||
|
||||
const kPermissionIDs = Object.keys(gPermissionObject);
|
||||
|
||||
|
@ -349,6 +349,19 @@ description#identity-popup-content-verifier,
|
||||
background-image: url(chrome://browser/skin/controlcenter/permissions.svg);
|
||||
}
|
||||
|
||||
#identity-popup-permissions-headline {
|
||||
/* Make sure the label is as tall as the icon so that the permission list
|
||||
which is aligned with the icon doesn't cover it up. */
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
#identity-popup-permission-list {
|
||||
/* Offset the padding set on #identity-popup-permissions-content so that it
|
||||
shows up just below the section. The permission icons are 16px wide and
|
||||
should be right aligned with the section icon. */
|
||||
margin-inline-start: calc(-1em - 16px);
|
||||
}
|
||||
|
||||
#identity-popup-permission-list menulist {
|
||||
min-width: 60px;
|
||||
}
|
||||
@ -361,8 +374,12 @@ description#identity-popup-content-verifier,
|
||||
display: none;
|
||||
}
|
||||
|
||||
.identity-popup-permission-label {
|
||||
margin-inline-start: 0;
|
||||
word-wrap: break-word;
|
||||
.identity-popup-permission-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.identity-popup-permission-label {
|
||||
margin-inline-start: 1em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
@ -22,5 +22,6 @@
|
||||
<path id="microphone" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
|
||||
<path id="microphone-detailed" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
|
||||
<path id="pointerLock" d="m 8,24 6,-5 5,10 4,-2 -5,-10 7,-1 -17,-14 z" />
|
||||
<path id="popup" d="m 2,24 a 4,4 0 0 0 4,4 l 8,0 a 10,10 0 0 1 -2,-4 l -4,0 a 2,2 0 0 1 -2,-2 l 0,-12 18,0 0,2 a 10,10 0 0 1 4,2 l 0,-8 a 4,4 0 0 0 -4,-4 l -18,0 a 4,4 0 0 0 -4,4 z m 12,-2.1 a 8,8 0 1 1 0,0.2 m 10.7,-4.3 a 5,5 0 0 0 -6.9,6.9 z m -5.4,8.4 a 5,5 0 0 0 6.9,-6.9 z" />
|
||||
<path id="screen" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.7 KiB |
@ -81,6 +81,7 @@
|
||||
.login-icon,
|
||||
.microphone-icon,
|
||||
.pointerLock-icon,
|
||||
.popup-icon,
|
||||
.screen-icon,
|
||||
.desktop-notification-icon,
|
||||
.popup-notification-icon[popupid="geolocation"],
|
||||
@ -179,6 +180,10 @@
|
||||
list-style-image: url(chrome://browser/skin/glyphs.svg#pointerLock);
|
||||
}
|
||||
|
||||
.popup-icon {
|
||||
list-style-image: url("chrome://browser/skin/glyphs.svg#popup");
|
||||
}
|
||||
|
||||
/* EME */
|
||||
|
||||
.popup-notification-icon[popupid="drmContentPlaying"],
|
||||
|
@ -1,8 +1,3 @@
|
||||
component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-startup.js
|
||||
contract @mozilla.org/devtools/startup-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
|
||||
# We want this to override toolkit's --jsconsole handling, so it must have a
|
||||
# a higher priority than the entry in jsconsole-clhandler.manifest. Higher
|
||||
# priority means the "m-devtools" value below needs to be something that sorts
|
||||
# before the one in jsconsole-clhandler.manifest. See details in
|
||||
# nsICommandLineHandler.idl.
|
||||
category command-line-handler m-devtools @mozilla.org/devtools/startup-clh;1
|
||||
|
@ -424,7 +424,7 @@ TabTarget.prototype = {
|
||||
this._title = this._form.title;
|
||||
|
||||
attachTab();
|
||||
});
|
||||
}, e => this._remote.reject(e));
|
||||
} else if (this.isTabActor) {
|
||||
// In the remote debugging case, the protocol connection will have been
|
||||
// already initialized in the connection screen code.
|
||||
|
@ -6,4 +6,6 @@
|
||||
|
||||
DevToolsModules(
|
||||
'swap.js',
|
||||
'tunnel.js',
|
||||
'web-navigation.js',
|
||||
)
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
const promise = require("promise");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { tunnelToInnerBrowser } = require("./tunnel");
|
||||
|
||||
/**
|
||||
* Swap page content from an existing tab into a new browser within a container
|
||||
@ -32,10 +33,14 @@ const { Task } = require("devtools/shared/task");
|
||||
function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) {
|
||||
let gBrowser = tab.ownerDocument.defaultView.gBrowser;
|
||||
let innerBrowser;
|
||||
let tunnel;
|
||||
|
||||
return {
|
||||
|
||||
start: Task.async(function* () {
|
||||
// Freeze navigation temporarily to avoid "blinking" in the location bar.
|
||||
freezeNavigationState(tab);
|
||||
|
||||
// 1. Create a temporary, hidden tab to load the tool UI.
|
||||
let containerTab = gBrowser.addTab(containerURL, {
|
||||
skipAnimation: true,
|
||||
@ -78,32 +83,47 @@ function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) {
|
||||
// original browser tab and close the temporary tab used to load the
|
||||
// tool via `swapBrowsersAndCloseOther`.
|
||||
gBrowser.swapBrowsersAndCloseOther(tab, containerTab);
|
||||
|
||||
// 7. Start a tunnel from the tool tab's browser to the viewport browser
|
||||
// so that some browser UI functions, like navigation, are connected to
|
||||
// the content in the viewport, instead of the tool page.
|
||||
tunnel = tunnelToInnerBrowser(tab.linkedBrowser, innerBrowser);
|
||||
yield tunnel.start();
|
||||
|
||||
// Force the browser UI to match the new state of the tab and browser.
|
||||
thawNavigationState(tab);
|
||||
gBrowser.setTabTitle(tab);
|
||||
gBrowser.updateCurrentBrowser(true);
|
||||
}),
|
||||
|
||||
stop() {
|
||||
// 1. Create a temporary, hidden tab to hold the content.
|
||||
// 1. Stop the tunnel between outer and inner browsers.
|
||||
tunnel.stop();
|
||||
tunnel = null;
|
||||
|
||||
// 2. Create a temporary, hidden tab to hold the content.
|
||||
let contentTab = gBrowser.addTab("about:blank", {
|
||||
skipAnimation: true,
|
||||
});
|
||||
gBrowser.hideTab(contentTab);
|
||||
let contentBrowser = contentTab.linkedBrowser;
|
||||
|
||||
// 2. Mark the content tab browser's docshell as active so the frame
|
||||
// 3. Mark the content tab browser's docshell as active so the frame
|
||||
// is created eagerly and will be ready to swap.
|
||||
contentBrowser.docShellIsActive = true;
|
||||
|
||||
// 3. Swap tab content from the browser within the viewport in the tool UI
|
||||
// 4. Swap tab content from the browser within the viewport in the tool UI
|
||||
// to the regular browser tab, preserving all state via
|
||||
// `gBrowser._swapBrowserDocShells`.
|
||||
gBrowser._swapBrowserDocShells(contentTab, innerBrowser);
|
||||
innerBrowser = null;
|
||||
|
||||
// 4. Force the original browser tab to be remote since web content is
|
||||
// 5. Force the original browser tab to be remote since web content is
|
||||
// loaded in the child process, and we're about to swap the content
|
||||
// into this tab.
|
||||
gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
|
||||
|
||||
// 5. Swap the content into the original browser tab and close the
|
||||
// 6. Swap the content into the original browser tab and close the
|
||||
// temporary tab used to hold the content via
|
||||
// `swapBrowsersAndCloseOther`.
|
||||
gBrowser.swapBrowsersAndCloseOther(tab, contentTab);
|
||||
@ -113,6 +133,40 @@ function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser navigation properties we'll freeze temporarily to avoid "blinking" in the
|
||||
* location bar, etc. caused by the containerURL peeking through before the swap is
|
||||
* complete.
|
||||
*/
|
||||
const NAVIGATION_PROPERTIES = [
|
||||
"currentURI",
|
||||
"contentTitle",
|
||||
"securityUI",
|
||||
];
|
||||
|
||||
function freezeNavigationState(tab) {
|
||||
// Browser navigation properties we'll freeze temporarily to avoid "blinking" in the
|
||||
// location bar, etc. caused by the containerURL peeking through before the swap is
|
||||
// complete.
|
||||
for (let property of NAVIGATION_PROPERTIES) {
|
||||
let value = tab.linkedBrowser[property];
|
||||
Object.defineProperty(tab.linkedBrowser, property, {
|
||||
get() {
|
||||
return value;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function thawNavigationState(tab) {
|
||||
// Thaw out the properties we froze at the beginning now that the swap is complete.
|
||||
for (let property of NAVIGATION_PROPERTIES) {
|
||||
delete tab.linkedBrowser[property];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser elements that are passed to `gBrowser._swapBrowserDocShells` are
|
||||
* expected to have certain properties that currently exist only on
|
||||
|
401
devtools/client/responsive.html/browser/tunnel.js
Normal file
@ -0,0 +1,401 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { BrowserElementWebNavigation } = require("./web-navigation");
|
||||
|
||||
function debug(msg) {
|
||||
// console.log(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties swapped between browsers by browser.xml's `swapDocShells`. See also the
|
||||
* list at /devtools/client/responsive.html/docs/browser-swap.md.
|
||||
*/
|
||||
const SWAPPED_BROWSER_STATE = [
|
||||
"_securityUI",
|
||||
"_documentURI",
|
||||
"_documentContentType",
|
||||
"_contentTitle",
|
||||
"_characterSet",
|
||||
"_contentPrincipal",
|
||||
"_imageDocument",
|
||||
"_fullZoom",
|
||||
"_textZoom",
|
||||
"_isSyntheticDocument",
|
||||
"_innerWindowID",
|
||||
"_manifestURI",
|
||||
];
|
||||
|
||||
/**
|
||||
* This module takes an "outer" <xul:browser> from a browser tab as described by
|
||||
* Firefox's tabbrowser.xml and wires it up to an "inner" <iframe mozbrowser>
|
||||
* browser element containing arbitrary page content of interest.
|
||||
*
|
||||
* The inner <iframe mozbrowser> element is _just_ the page content. It is not
|
||||
* enough to to replace <xul:browser> on its own. <xul:browser> comes along
|
||||
* with lots of associated functionality via XBL bindings defined for such
|
||||
* elements in browser.xml and remote-browser.xml, and the Firefox UI depends on
|
||||
* these various things to make the UI function.
|
||||
*
|
||||
* By mapping various methods, properties, and messages from the outer browser
|
||||
* to the inner browser, we can control the content inside the inner browser
|
||||
* using the standard Firefox UI elements for navigation, reloading, and more.
|
||||
*
|
||||
* The approaches used in this module were chosen to avoid needing changes to
|
||||
* the core browser for this specialized use case. If we start to increase
|
||||
* usage of <iframe mozbrowser> in the core browser, we should avoid this module
|
||||
* and instead refactor things to work with mozbrowser directly.
|
||||
*
|
||||
* For the moment though, this serves as a sufficient path to connect the
|
||||
* Firefox UI to a mozbrowser.
|
||||
*
|
||||
* @param outer
|
||||
* A <xul:browser> from a regular browser tab.
|
||||
* @param inner
|
||||
* A <iframe mozbrowser> containing page content to be wired up to the
|
||||
* primary browser UI via the outer browser.
|
||||
*/
|
||||
function tunnelToInnerBrowser(outer, inner) {
|
||||
let browserWindow = outer.ownerDocument.defaultView;
|
||||
let gBrowser = browserWindow.gBrowser;
|
||||
let mmTunnel;
|
||||
|
||||
return {
|
||||
|
||||
start: Task.async(function* () {
|
||||
if (outer.isRemoteBrowser) {
|
||||
throw new Error("The outer browser must be non-remote.");
|
||||
}
|
||||
if (!inner.isRemoteBrowser) {
|
||||
throw new Error("The inner browser must be remote.");
|
||||
}
|
||||
|
||||
// The `permanentKey` property on a <xul:browser> is used to index into various maps
|
||||
// held by the session store. When you swap content around with
|
||||
// `_swapBrowserDocShells`, these keys are also swapped so they follow the content.
|
||||
// This means the key that matches the content is on the inner browser. Since we
|
||||
// want the browser UI to believe the page content is part of the outer browser, we
|
||||
// copy the content's `permanentKey` up to the outer browser.
|
||||
copyPermanentKey(outer, inner);
|
||||
|
||||
// Replace the outer browser's native messageManager with a message manager tunnel
|
||||
// which we can use to route messages of interest to the inner browser instead.
|
||||
// Note: The _actual_ messageManager accessible from
|
||||
// `browser.frameLoader.messageManager` is not overridable and is left unchanged.
|
||||
// Only the XBL getter `browser.messageManager` is overridden. Browser UI code
|
||||
// always uses this getter instead of `browser.frameLoader.messageManager` directly,
|
||||
// so this has the effect of overriding the message manager for browser UI code.
|
||||
mmTunnel = new MessageManagerTunnel(outer, inner);
|
||||
Object.defineProperty(outer, "messageManager", {
|
||||
value: mmTunnel,
|
||||
writable: false,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
// We are tunneling to an inner browser with a specific remoteness, so it is simpler
|
||||
// for the logic of the browser UI to assume this tab has taken on that remoteness,
|
||||
// even though it's not true. Since the actions the browser UI performs are sent
|
||||
// down to the inner browser by this tunnel, the tab's remoteness effectively is the
|
||||
// remoteness of the inner browser.
|
||||
Object.defineProperty(outer, "isRemoteBrowser", {
|
||||
get() {
|
||||
return true;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
// Clear out any cached state that references the current non-remote XBL binding,
|
||||
// such as form fill controllers. Otherwise they will remain in place and leak the
|
||||
// outer docshell.
|
||||
outer.destroy();
|
||||
// The XBL binding for remote browsers uses the message manager for many actions in
|
||||
// the UI and that works well here, since it gives us one main thing we need to
|
||||
// route to the inner browser (the messages), instead of having to tweak many
|
||||
// different browser properties. It is safe to alter a XBL binding dynamically.
|
||||
// The content within is not reloaded.
|
||||
outer.style.MozBinding = "url(chrome://browser/content/tabbrowser.xml" +
|
||||
"#tabbrowser-remote-browser)";
|
||||
|
||||
// The constructor of the new XBL binding is run asynchronously and there is no
|
||||
// event to signal its completion. Spin an event loop to watch for properties that
|
||||
// are set by the contructor.
|
||||
while (!outer._remoteWebNavigation) {
|
||||
Services.tm.currentThread.processNextEvent(true);
|
||||
}
|
||||
|
||||
// Replace the `webNavigation` object with our own version which tries to use
|
||||
// mozbrowser APIs where possible. This replaces the webNavigation object that the
|
||||
// remote-browser.xml binding creates. We do not care about it's original value
|
||||
// because stop() will remove the remote-browser.xml binding and these will no
|
||||
// longer be used.
|
||||
let webNavigation = new BrowserElementWebNavigation(inner);
|
||||
webNavigation.copyStateFrom(inner._remoteWebNavigationImpl);
|
||||
outer._remoteWebNavigation = webNavigation;
|
||||
outer._remoteWebNavigationImpl = webNavigation;
|
||||
|
||||
// Now that we've flipped to the remote browser XBL binding, add `progressListener`
|
||||
// onto the remote version of `webProgress`. Normally tabbrowser.xml does this step
|
||||
// when it creates a new browser, etc. Since we manually changed the XBL binding
|
||||
// above, it caused a fresh webProgress object to be created which does not have any
|
||||
// listeners added. So, we get the listener that gBrowser is using for the tab and
|
||||
// reattach it here.
|
||||
let tab = gBrowser.getTabForBrowser(outer);
|
||||
let filteredProgressListener = gBrowser._tabFilters.get(tab);
|
||||
outer.webProgress.addProgressListener(filteredProgressListener);
|
||||
|
||||
// All of the browser state from content was swapped onto the inner browser. Pull
|
||||
// this state up to the outer browser.
|
||||
for (let property of SWAPPED_BROWSER_STATE) {
|
||||
outer[property] = inner[property];
|
||||
}
|
||||
|
||||
// Wants to access the content's `frameLoader`, so we'll redirect it to
|
||||
// inner browser.
|
||||
Object.defineProperty(outer, "hasContentOpener", {
|
||||
get() {
|
||||
return inner.frameLoader.tabParent.hasContentOpener;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
// Wants to access the content's `frameLoader`, so we'll redirect it to
|
||||
// inner browser.
|
||||
Object.defineProperty(outer, "docShellIsActive", {
|
||||
get() {
|
||||
return inner.frameLoader.tabParent.docShellIsActive;
|
||||
},
|
||||
set(value) {
|
||||
inner.frameLoader.tabParent.docShellIsActive = value;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
// Wants to access the content's `frameLoader`, so we'll redirect it to
|
||||
// inner browser.
|
||||
outer.setDocShellIsActiveAndForeground = value => {
|
||||
inner.frameLoader.tabParent.setDocShellIsActiveAndForeground(value);
|
||||
};
|
||||
}),
|
||||
|
||||
stop() {
|
||||
let tab = gBrowser.getTabForBrowser(outer);
|
||||
let filteredProgressListener = gBrowser._tabFilters.get(tab);
|
||||
browserWindow = null;
|
||||
gBrowser = null;
|
||||
|
||||
// The browser's state has changed over time while the tunnel was active. Push the
|
||||
// the current state down to the inner browser, so that it follows the content in
|
||||
// case that browser will be swapped elsewhere.
|
||||
for (let property of SWAPPED_BROWSER_STATE) {
|
||||
inner[property] = outer[property];
|
||||
}
|
||||
|
||||
// Remove the progress listener we added manually.
|
||||
outer.webProgress.removeProgressListener(filteredProgressListener);
|
||||
|
||||
// Reset the XBL binding back to the default.
|
||||
outer.destroy();
|
||||
outer.style.MozBinding = "";
|
||||
|
||||
// Reset overridden XBL properties and methods. Deleting the override
|
||||
// means it will fallback to the original XBL binding definitions which
|
||||
// are on the prototype.
|
||||
delete outer.messageManager;
|
||||
delete outer.isRemoteBrowser;
|
||||
delete outer.hasContentOpener;
|
||||
delete outer.docShellIsActive;
|
||||
delete outer.setDocShellIsActiveAndForeground;
|
||||
|
||||
mmTunnel.destroy();
|
||||
mmTunnel = null;
|
||||
|
||||
// Invalidate outer's permanentKey so that SessionStore stops associating
|
||||
// things that happen to the outer browser with the content inside in the
|
||||
// inner browser.
|
||||
outer.permanentKey = { id: "zombie" };
|
||||
},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
exports.tunnelToInnerBrowser = tunnelToInnerBrowser;
|
||||
|
||||
function copyPermanentKey(outer, inner) {
|
||||
// When we're in the process of swapping content around, we end up receiving a
|
||||
// SessionStore:update message which lists the container page that is loaded into the
|
||||
// outer browser (that we're hiding the inner browser within) as part of its history.
|
||||
// We want SessionStore's view of the history for our tab to only have the page content
|
||||
// of the inner browser, so we want to hide this message from SessionStore, but we have
|
||||
// no direct mechanism to do so. As a workaround, we wait until the one errant message
|
||||
// has gone by, and then we copy the permanentKey after that, since the permanentKey is
|
||||
// what SessionStore uses to identify each browser.
|
||||
let outerMM = outer.frameLoader.messageManager;
|
||||
let onHistoryEntry = message => {
|
||||
let history = message.data.data.history;
|
||||
if (!history || !history.entries) {
|
||||
// Wait for a message that contains history data
|
||||
return;
|
||||
}
|
||||
outerMM.removeMessageListener("SessionStore:update", onHistoryEntry);
|
||||
debug("Got session update for outer browser");
|
||||
DevToolsUtils.executeSoon(() => {
|
||||
debug("Copy inner permanentKey to outer browser");
|
||||
outer.permanentKey = inner.permanentKey;
|
||||
});
|
||||
};
|
||||
outerMM.addMessageListener("SessionStore:update", onHistoryEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* This module allows specific messages of interest to be directed from the
|
||||
* outer browser to the inner browser (and vice versa) in a targetted fashion
|
||||
* without having to touch the original code paths that use them.
|
||||
*/
|
||||
function MessageManagerTunnel(outer, inner) {
|
||||
if (outer.isRemoteBrowser) {
|
||||
throw new Error("The outer browser must be non-remote.");
|
||||
}
|
||||
this.outer = outer;
|
||||
this.inner = inner;
|
||||
this.init();
|
||||
}
|
||||
|
||||
MessageManagerTunnel.prototype = {
|
||||
|
||||
/**
|
||||
* Most message manager methods are left alone and are just passed along to
|
||||
* the outer browser's real message manager. `sendAsyncMessage` is only one
|
||||
* with special behavior.
|
||||
*/
|
||||
PASS_THROUGH_METHODS: [
|
||||
"addMessageListener",
|
||||
"loadFrameScript",
|
||||
"killChild",
|
||||
"assertPermission",
|
||||
"assertContainApp",
|
||||
"assertAppHasPermission",
|
||||
"assertAppHasStatus",
|
||||
"removeDelayedFrameScript",
|
||||
"getDelayedFrameScripts",
|
||||
"loadProcessScript",
|
||||
"removeDelayedProcessScript",
|
||||
"getDelayedProcessScripts",
|
||||
"removeMessageListener",
|
||||
"addWeakMessageListener",
|
||||
"removeWeakMessageListener",
|
||||
],
|
||||
|
||||
OUTER_TO_INNER_MESSAGES: [
|
||||
// Messages sent from remote-browser.xml
|
||||
"Browser:PurgeSessionHistory",
|
||||
"InPermitUnload",
|
||||
"PermitUnload",
|
||||
// Messages sent from browser.js
|
||||
"Browser:Reload",
|
||||
// Messages sent from SelectParentHelper.jsm
|
||||
"Forms:DismissedDropDown",
|
||||
"Forms:MouseOut",
|
||||
"Forms:MouseOver",
|
||||
"Forms:SelectDropDownItem",
|
||||
// Messages sent from SessionStore.jsm
|
||||
"SessionStore:flush",
|
||||
],
|
||||
|
||||
INNER_TO_OUTER_MESSAGES: [
|
||||
// Messages sent to RemoteWebProgress.jsm
|
||||
"Content:LoadURIResult",
|
||||
"Content:LocationChange",
|
||||
"Content:ProgressChange",
|
||||
"Content:SecurityChange",
|
||||
"Content:StateChange",
|
||||
"Content:StatusChange",
|
||||
// Messages sent to remote-browser.xml
|
||||
"DOMTitleChanged",
|
||||
"ImageDocumentLoaded",
|
||||
"Forms:ShowDropDown",
|
||||
"Forms:HideDropDown",
|
||||
"InPermitUnload",
|
||||
"PermitUnload",
|
||||
// Messages sent to SelectParentHelper.jsm
|
||||
"Forms:UpdateDropDown",
|
||||
// Messages sent to browser.js
|
||||
"PageVisibility:Hide",
|
||||
"PageVisibility:Show",
|
||||
// Messages sent to SessionStore.jsm
|
||||
"SessionStore:update",
|
||||
// Messages sent to BrowserTestUtils.jsm
|
||||
"browser-test-utils:loadEvent",
|
||||
],
|
||||
|
||||
get outerParentMM() {
|
||||
return this.outer.frameLoader.messageManager;
|
||||
},
|
||||
|
||||
get outerChildMM() {
|
||||
// This is only possible because we require the outer browser to be
|
||||
// non-remote, so we're able to reach into its window and use the child
|
||||
// side message manager there.
|
||||
let docShell = this.outer.frameLoader.docShell;
|
||||
return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
},
|
||||
|
||||
get innerParentMM() {
|
||||
return this.inner.frameLoader.messageManager;
|
||||
},
|
||||
|
||||
sendAsyncMessage(name, ...args) {
|
||||
debug(`Calling sendAsyncMessage for ${name}`);
|
||||
|
||||
if (!this.OUTER_TO_INNER_MESSAGES.includes(name)) {
|
||||
debug(`Should ${name} go to inner?`);
|
||||
this.outerParentMM.sendAsyncMessage(name, ...args);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`${name} outer -> inner`);
|
||||
this.innerParentMM.sendAsyncMessage(name, ...args);
|
||||
},
|
||||
|
||||
init() {
|
||||
for (let method of this.PASS_THROUGH_METHODS) {
|
||||
// Workaround bug 449811 to ensure a fresh binding each time through the loop
|
||||
let _method = method;
|
||||
this[_method] = (...args) => {
|
||||
return this.outerParentMM[_method](...args);
|
||||
};
|
||||
}
|
||||
|
||||
for (let message of this.INNER_TO_OUTER_MESSAGES) {
|
||||
this.innerParentMM.addMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
for (let message of this.INNER_TO_OUTER_MESSAGES) {
|
||||
this.innerParentMM.removeMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage({ name, data, objects, principal }) {
|
||||
if (!this.INNER_TO_OUTER_MESSAGES.includes(name)) {
|
||||
debug(`Received unexpected message ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`${name} inner -> outer`);
|
||||
this.outerChildMM.sendAsyncMessage(name, data, objects, principal);
|
||||
},
|
||||
|
||||
};
|
179
devtools/client/responsive.html/browser/web-navigation.js
Normal file
@ -0,0 +1,179 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Ci, Cu, Cr } = require("chrome");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const Services = require("Services");
|
||||
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
function readInputStreamToString(stream) {
|
||||
return NetUtil.readInputStreamToString(stream, stream.available());
|
||||
}
|
||||
|
||||
/**
|
||||
* This object aims to provide the nsIWebNavigation interface for mozbrowser elements.
|
||||
* nsIWebNavigation is one of the interfaces expected on <xul:browser>s, so this wrapper
|
||||
* helps mozbrowser elements support this.
|
||||
*
|
||||
* It attempts to use the mozbrowser API wherever possible, however some methods don't
|
||||
* exist yet, so we fallback to the WebNavigation frame script messages in those cases.
|
||||
* Ideally the mozbrowser API would eventually be extended to cover all properties and
|
||||
* methods used here.
|
||||
*
|
||||
* This is largely copied from RemoteWebNavigation.js, which uses the message manager to
|
||||
* perform all actions.
|
||||
*/
|
||||
function BrowserElementWebNavigation(browser) {
|
||||
this._browser = browser;
|
||||
}
|
||||
|
||||
BrowserElementWebNavigation.prototype = {
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIWebNavigation,
|
||||
Ci.nsISupports
|
||||
]),
|
||||
|
||||
get _mm() {
|
||||
return this._browser.frameLoader.messageManager;
|
||||
},
|
||||
|
||||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
|
||||
goBack() {
|
||||
this._browser.goBack();
|
||||
},
|
||||
|
||||
goForward() {
|
||||
this._browser.goForward();
|
||||
},
|
||||
|
||||
gotoIndex(index) {
|
||||
// No equivalent in the current BrowserElement API
|
||||
this._sendMessage("WebNavigation:GotoIndex", { index });
|
||||
},
|
||||
|
||||
loadURI(uri, flags, referrer, postData, headers) {
|
||||
// No equivalent in the current BrowserElement API
|
||||
this.loadURIWithOptions(uri, flags, referrer,
|
||||
Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
|
||||
postData, headers, null);
|
||||
},
|
||||
|
||||
loadURIWithOptions(uri, flags, referrer, referrerPolicy, postData, headers,
|
||||
baseURI) {
|
||||
// No equivalent in the current BrowserElement API
|
||||
this._sendMessage("WebNavigation:LoadURI", {
|
||||
uri,
|
||||
flags,
|
||||
referrer: referrer ? referrer.spec : null,
|
||||
referrerPolicy: referrerPolicy,
|
||||
postData: postData ? readInputStreamToString(postData) : null,
|
||||
headers: headers ? readInputStreamToString(headers) : null,
|
||||
baseURI: baseURI ? baseURI.spec : null,
|
||||
});
|
||||
},
|
||||
|
||||
setOriginAttributesBeforeLoading(originAttributes) {
|
||||
// No equivalent in the current BrowserElement API
|
||||
this._sendMessage("WebNavigation:SetOriginAttributes", {
|
||||
originAttributes,
|
||||
});
|
||||
},
|
||||
|
||||
reload(flags) {
|
||||
let hardReload = false;
|
||||
if (flags & this.LOAD_FLAGS_BYPASS_PROXY ||
|
||||
flags & this.LOAD_FLAGS_BYPASS_CACHE) {
|
||||
hardReload = true;
|
||||
}
|
||||
this._browser.reload(hardReload);
|
||||
},
|
||||
|
||||
stop(flags) {
|
||||
// No equivalent in the current BrowserElement API
|
||||
this._sendMessage("WebNavigation:Stop", { flags });
|
||||
},
|
||||
|
||||
get document() {
|
||||
return this._browser.contentDocument;
|
||||
},
|
||||
|
||||
_currentURI: null,
|
||||
get currentURI() {
|
||||
if (!this._currentURI) {
|
||||
this._currentURI = Services.io.newURI("about:blank", null, null);
|
||||
}
|
||||
return this._currentURI;
|
||||
},
|
||||
set currentURI(uri) {
|
||||
this._browser.src = uri.spec;
|
||||
},
|
||||
|
||||
referringURI: null,
|
||||
|
||||
// Bug 1233803 - accessing the sessionHistory of remote browsers should be
|
||||
// done in content scripts.
|
||||
get sessionHistory() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
set sessionHistory(value) {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
_sendMessage(message, data) {
|
||||
try {
|
||||
this._mm.sendAsyncMessage(message, data);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
swapBrowser(browser) {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
copyStateFrom(otherWebNavigation) {
|
||||
const state = [
|
||||
"canGoBack",
|
||||
"canGoForward",
|
||||
"_currentURI",
|
||||
];
|
||||
for (let property of state) {
|
||||
this[property] = otherWebNavigation[property];
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const FLAGS = [
|
||||
"LOAD_FLAGS_MASK",
|
||||
"LOAD_FLAGS_NONE",
|
||||
"LOAD_FLAGS_IS_REFRESH",
|
||||
"LOAD_FLAGS_IS_LINK",
|
||||
"LOAD_FLAGS_BYPASS_HISTORY",
|
||||
"LOAD_FLAGS_REPLACE_HISTORY",
|
||||
"LOAD_FLAGS_BYPASS_CACHE",
|
||||
"LOAD_FLAGS_BYPASS_PROXY",
|
||||
"LOAD_FLAGS_CHARSET_CHANGE",
|
||||
"LOAD_FLAGS_STOP_CONTENT",
|
||||
"LOAD_FLAGS_FROM_EXTERNAL",
|
||||
"LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP",
|
||||
"LOAD_FLAGS_FIRST_LOAD",
|
||||
"LOAD_FLAGS_ALLOW_POPUPS",
|
||||
"LOAD_FLAGS_BYPASS_CLASSIFIER",
|
||||
"LOAD_FLAGS_FORCE_ALLOW_COOKIES",
|
||||
"STOP_NETWORK",
|
||||
"STOP_CONTENT",
|
||||
"STOP_ALL",
|
||||
];
|
||||
|
||||
for (let flag of FLAGS) {
|
||||
BrowserElementWebNavigation.prototype[flag] = Ci.nsIWebNavigation[flag];
|
||||
}
|
||||
|
||||
exports.BrowserElementWebNavigation = BrowserElementWebNavigation;
|
@ -19,6 +19,7 @@ support-files =
|
||||
[browser_menu_item_01.js]
|
||||
[browser_menu_item_02.js]
|
||||
[browser_mouse_resize.js]
|
||||
[browser_navigation.js]
|
||||
[browser_page_state.js]
|
||||
[browser_resize_cmd.js]
|
||||
skip-if = true # GCLI target confused after swap, will fix in bug 1240907
|
||||
|
@ -0,0 +1,98 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the primary browser navigation UI to verify it's connected to the viewport.
|
||||
|
||||
const DUMMY_1_URL = "http://example.com/";
|
||||
const TEST_URL = `${URL_ROOT}doc_page_state.html`;
|
||||
const DUMMY_2_URL = "http://example.com/browser/";
|
||||
const DUMMY_3_URL = "http://example.com/browser/devtools/";
|
||||
|
||||
add_task(function* () {
|
||||
// Load up a sequence of pages:
|
||||
// 0. DUMMY_1_URL
|
||||
// 1. TEST_URL
|
||||
// 2. DUMMY_2_URL
|
||||
let tab = yield addTab(DUMMY_1_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield load(browser, TEST_URL);
|
||||
yield load(browser, DUMMY_2_URL);
|
||||
|
||||
// Check session history state
|
||||
let history = yield getSessionHistory(browser);
|
||||
is(history.index, 2, "At page 2 in history");
|
||||
is(history.entries.length, 3, "3 pages in history");
|
||||
is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches");
|
||||
is(history.entries[1].uri, TEST_URL, "Page 1 URL matches");
|
||||
is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches");
|
||||
|
||||
// Go back one so we're at the test page
|
||||
yield back(browser);
|
||||
|
||||
// Check session history state
|
||||
history = yield getSessionHistory(browser);
|
||||
is(history.index, 1, "At page 1 in history");
|
||||
is(history.entries.length, 3, "3 pages in history");
|
||||
is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches");
|
||||
is(history.entries[1].uri, TEST_URL, "Page 1 URL matches");
|
||||
is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches");
|
||||
|
||||
yield openRDM(tab);
|
||||
|
||||
ok(browser.webNavigation.canGoBack, "Going back is allowed");
|
||||
ok(browser.webNavigation.canGoForward, "Going forward is allowed");
|
||||
is(browser.documentURI.spec, TEST_URL, "documentURI matches page 1");
|
||||
is(browser.contentTitle, "Page State Test", "contentTitle matches page 1");
|
||||
|
||||
yield forward(browser);
|
||||
|
||||
ok(browser.webNavigation.canGoBack, "Going back is allowed");
|
||||
ok(!browser.webNavigation.canGoForward, "Going forward is not allowed");
|
||||
is(browser.documentURI.spec, DUMMY_2_URL, "documentURI matches page 2");
|
||||
is(browser.contentTitle, "mochitest index /browser/", "contentTitle matches page 2");
|
||||
|
||||
yield back(browser);
|
||||
yield back(browser);
|
||||
|
||||
ok(!browser.webNavigation.canGoBack, "Going back is not allowed");
|
||||
ok(browser.webNavigation.canGoForward, "Going forward is allowed");
|
||||
is(browser.documentURI.spec, DUMMY_1_URL, "documentURI matches page 0");
|
||||
is(browser.contentTitle, "mochitest index /", "contentTitle matches page 0");
|
||||
|
||||
let receivedStatusChanges = new Promise(resolve => {
|
||||
let statusChangesSeen = 0;
|
||||
let statusChangesExpected = 2;
|
||||
let progressListener = {
|
||||
onStatusChange(webProgress, request, status, message) {
|
||||
info(message);
|
||||
if (++statusChangesSeen == statusChangesExpected) {
|
||||
gBrowser.removeProgressListener(progressListener);
|
||||
ok(true, `${statusChangesExpected} status changes while loading`);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
gBrowser.addProgressListener(progressListener);
|
||||
});
|
||||
yield load(browser, DUMMY_3_URL);
|
||||
yield receivedStatusChanges;
|
||||
|
||||
ok(browser.webNavigation.canGoBack, "Going back is allowed");
|
||||
ok(!browser.webNavigation.canGoForward, "Going forward is not allowed");
|
||||
is(browser.documentURI.spec, DUMMY_3_URL, "documentURI matches page 3");
|
||||
is(browser.contentTitle, "mochitest index /browser/devtools/",
|
||||
"contentTitle matches page 3");
|
||||
|
||||
yield closeRDM(tab);
|
||||
|
||||
// Check session history state
|
||||
history = yield getSessionHistory(browser);
|
||||
is(history.index, 1, "At page 1 in history");
|
||||
is(history.entries.length, 2, "2 pages in history");
|
||||
is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches");
|
||||
is(history.entries[1].uri, DUMMY_3_URL, "Page 1 URL matches");
|
||||
|
||||
yield removeTab(tab);
|
||||
});
|
@ -17,14 +17,8 @@ add_task(function* () {
|
||||
// 2. DUMMY_2_URL
|
||||
let tab = yield addTab(DUMMY_1_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_URL);
|
||||
browser.loadURI(TEST_URL, null, null);
|
||||
yield loaded;
|
||||
|
||||
loaded = BrowserTestUtils.browserLoaded(browser, false, DUMMY_2_URL);
|
||||
browser.loadURI(DUMMY_2_URL, null, null);
|
||||
yield loaded;
|
||||
yield load(browser, TEST_URL);
|
||||
yield load(browser, DUMMY_2_URL);
|
||||
|
||||
// Check session history state
|
||||
let history = yield getSessionHistory(browser);
|
||||
@ -35,9 +29,7 @@ add_task(function* () {
|
||||
is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches");
|
||||
|
||||
// Go back one so we're at the test page
|
||||
let shown = waitForPageShow(browser);
|
||||
browser.goBack();
|
||||
yield shown;
|
||||
yield back(browser);
|
||||
|
||||
// Check session history state
|
||||
history = yield getSessionHistory(browser);
|
||||
@ -82,41 +74,3 @@ add_task(function* () {
|
||||
|
||||
yield removeTab(tab);
|
||||
});
|
||||
|
||||
function getSessionHistory(browser) {
|
||||
return ContentTask.spawn(browser, {}, function* () {
|
||||
/* eslint-disable no-undef */
|
||||
let { interfaces: Ci } = Components;
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let sessionHistory = webNav.sessionHistory;
|
||||
let result = {
|
||||
index: sessionHistory.index,
|
||||
entries: []
|
||||
};
|
||||
|
||||
for (let i = 0; i < sessionHistory.count; i++) {
|
||||
let entry = sessionHistory.getEntryAtIndex(i, false);
|
||||
result.entries.push({
|
||||
uri: entry.URI.spec,
|
||||
title: entry.title
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
/* eslint-enable no-undef */
|
||||
});
|
||||
}
|
||||
|
||||
function waitForPageShow(browser) {
|
||||
let mm = browser.messageManager;
|
||||
return new Promise(resolve => {
|
||||
let onShow = message => {
|
||||
if (message.target != browser) {
|
||||
return;
|
||||
}
|
||||
mm.removeMessageListener("PageVisibility:Show", onShow);
|
||||
resolve();
|
||||
};
|
||||
mm.addMessageListener("PageVisibility:Show", onShow);
|
||||
});
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
background: red;
|
||||
}
|
||||
body.modified {
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
<head>
|
||||
<title>Page State Test</title>
|
||||
<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
background: red;
|
||||
}
|
||||
body.modified {
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onclick="this.classList.add('modified')"/>
|
||||
</html>
|
||||
|
@ -153,3 +153,59 @@ function openDeviceModal(ui) {
|
||||
ok(!modal.classList.contains("hidden"),
|
||||
"The device modal is displayed.");
|
||||
}
|
||||
|
||||
function getSessionHistory(browser) {
|
||||
return ContentTask.spawn(browser, {}, function* () {
|
||||
/* eslint-disable no-undef */
|
||||
let { interfaces: Ci } = Components;
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let sessionHistory = webNav.sessionHistory;
|
||||
let result = {
|
||||
index: sessionHistory.index,
|
||||
entries: []
|
||||
};
|
||||
|
||||
for (let i = 0; i < sessionHistory.count; i++) {
|
||||
let entry = sessionHistory.getEntryAtIndex(i, false);
|
||||
result.entries.push({
|
||||
uri: entry.URI.spec,
|
||||
title: entry.title
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
/* eslint-enable no-undef */
|
||||
});
|
||||
}
|
||||
|
||||
function waitForPageShow(browser) {
|
||||
let mm = browser.messageManager;
|
||||
return new Promise(resolve => {
|
||||
let onShow = message => {
|
||||
if (message.target != browser) {
|
||||
return;
|
||||
}
|
||||
mm.removeMessageListener("PageVisibility:Show", onShow);
|
||||
resolve();
|
||||
};
|
||||
mm.addMessageListener("PageVisibility:Show", onShow);
|
||||
});
|
||||
}
|
||||
|
||||
function load(browser, url) {
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
|
||||
browser.loadURI(url, null, null);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
function back(browser) {
|
||||
let shown = waitForPageShow(browser);
|
||||
browser.goBack();
|
||||
return shown;
|
||||
}
|
||||
|
||||
function forward(browser) {
|
||||
let shown = waitForPageShow(browser);
|
||||
browser.goForward();
|
||||
return shown;
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ support-files =
|
||||
[test_notification_box_01.html]
|
||||
[test_notification_box_02.html]
|
||||
[test_notification_box_03.html]
|
||||
[test_reps_attribute.html]
|
||||
[test_reps_date-time.html]
|
||||
[test_reps_grip.html]
|
||||
[test_reps_object-with-url.html]
|
||||
[test_reps_stylesheet.html]
|
||||
[test_reps_undefined.html]
|
||||
|
@ -0,0 +1,54 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test Attribute rep
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rep test - Attribute</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||
let { Attribute } = browserRequire("devtools/client/shared/components/reps/attribute");
|
||||
|
||||
let gripStub = {
|
||||
"type": "object",
|
||||
"class": "Attr",
|
||||
"actor": "server1.conn19.obj65",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 0,
|
||||
"preview": {
|
||||
"kind": "DOMNode",
|
||||
"nodeType": 2,
|
||||
"nodeName": "class",
|
||||
"value": "autocomplete-suggestions"
|
||||
}
|
||||
};
|
||||
|
||||
// Test that correct rep is chosen
|
||||
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
|
||||
is(renderedRep.type, Attribute.rep, `Rep correctly selects ${Attribute.rep.displayName}`);
|
||||
|
||||
// Test rendering
|
||||
const renderedComponent = renderComponent(Attribute.rep, { object: gripStub });
|
||||
is(renderedComponent.textContent, "class=\"autocomplete-suggestions\"", "Attribute rep has expected text content");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,435 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test grip rep
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rep test - grip</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||
let { Grip } = browserRequire("devtools/client/shared/components/reps/grip");
|
||||
|
||||
try {
|
||||
yield testBasic();
|
||||
|
||||
// Test property iterator
|
||||
yield testMaxProps();
|
||||
yield testMoreThanMaxProps();
|
||||
yield testUninterestingProps();
|
||||
|
||||
// Test that properties are rendered as expected by PropRep
|
||||
yield testNestedObject();
|
||||
yield testNestedArray();
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function testBasic() {
|
||||
// Test object: `{}`
|
||||
const testName = "testBasic";
|
||||
|
||||
// Test that correct rep is chosen
|
||||
const gripStub = getGripStub("testBasic");
|
||||
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
|
||||
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
|
||||
|
||||
// Test rendering
|
||||
const defaultOutput = `Object`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRenderingInMode(modeTests, testName);
|
||||
}
|
||||
|
||||
function testMaxProps() {
|
||||
// Test object: `{a: "a", b: "b", c: "c"}`;
|
||||
const testName = "testMaxProps";
|
||||
|
||||
const defaultOutput = `Object {a: "a", b: "b", c: "c"}`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRenderingInMode(modeTests, testName);
|
||||
}
|
||||
|
||||
function testMoreThanMaxProps() {
|
||||
// Test object = `{a: "a", b: "b", c: "c", d: "d", e: "e"}`
|
||||
const testName = "testMoreThanMaxProps";
|
||||
|
||||
const defaultOutput = `Object {a: "a", b: "b", c: "c", more...}`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
// @TODO Fix this test case.
|
||||
// See Bug 1281489 - Reps: Grips rep renders only 3 properties in long mode
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRenderingInMode(modeTests, testName);
|
||||
}
|
||||
|
||||
function testUninterestingProps() {
|
||||
// Test object: `{a: undefined, b: undefined, c: "c", d: 1}`
|
||||
// @TODO This is not how we actually want the preview to be output.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1276376
|
||||
const expectedOutput = `Object {a: undefined, b: undefined, c: "c", more...}`;
|
||||
}
|
||||
|
||||
function testNestedObject() {
|
||||
// Test object: `{objProp: {id: 1}, strProp: "test string"}`
|
||||
const testName = "testNestedObject";
|
||||
|
||||
const defaultOutput = `Object {objProp: Object, strProp: "test string"}`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRenderingInMode(modeTests, testName);
|
||||
}
|
||||
|
||||
function testNestedArray() {
|
||||
// Test object: `{arrProp: ["foo", "bar", "baz"]}`
|
||||
const testName = "testNestedArray";
|
||||
|
||||
const defaultOutput = `Object {arrProp: [3]}`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRenderingInMode(modeTests, testName);
|
||||
}
|
||||
|
||||
function testRenderingInMode(modeTests, testName) {
|
||||
modeTests.forEach(({mode, expectedOutput, message}) => {
|
||||
const modeString = typeof mode === "undefined" ? "no mode" : mode;
|
||||
if (!message) {
|
||||
message = `${testName}: ${modeString} renders correctly.`
|
||||
}
|
||||
const gripStub = getGripStub(testName);
|
||||
|
||||
const rendered = renderComponent(Grip.rep, { object: gripStub, mode });
|
||||
is(rendered.textContent, expectedOutput, message);
|
||||
});
|
||||
}
|
||||
|
||||
function getGripStub(functionName) {
|
||||
switch (functionName) {
|
||||
case "testBasic":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj304",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 0,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {},
|
||||
"ownPropertiesLength": 0,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
case "testMaxProps":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj337",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 3,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"a": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "a"
|
||||
},
|
||||
"b": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "b"
|
||||
},
|
||||
"c": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "c"
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 3,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
case "testMoreThanMaxProps":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj332",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 5,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"a": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "a"
|
||||
},
|
||||
"b": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "b"
|
||||
},
|
||||
"c": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "c"
|
||||
},
|
||||
"d": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "d"
|
||||
},
|
||||
"e": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "e"
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 5,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
case "testUninterestingProps":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj342",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 4,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"a": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": {
|
||||
"type": "undefined"
|
||||
}
|
||||
},
|
||||
"b": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": {
|
||||
"type": "undefined"
|
||||
}
|
||||
},
|
||||
"c": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "c"
|
||||
},
|
||||
"d": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": 1
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 4,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
case "testNestedObject":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj145",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 2,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"objProp": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj146",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 1
|
||||
}
|
||||
},
|
||||
"strProp": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": "test string"
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 2,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
case "testNestedArray":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj326",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 1,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"arrProp": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": {
|
||||
"type": "object",
|
||||
"class": "Array",
|
||||
"actor": "server1.conn0.obj327",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 4,
|
||||
"preview": {
|
||||
"kind": "ArrayLike",
|
||||
"length": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 1,
|
||||
"safeGetterValues": {}
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -33,21 +33,25 @@ extra state that may be set on tab by add-ons or others.
|
||||
6. Swap the tool UI (with viewport showing the content) into the original
|
||||
browser tab and close the temporary tab used to load the tool via
|
||||
`swapBrowsersAndCloseOther`.
|
||||
7. Start a tunnel from the tool tab's browser to the viewport browser
|
||||
so that some browser UI functions, like navigation, are connected to
|
||||
the content in the viewport, instead of the tool page.
|
||||
|
||||
## Closing RDM During Current Firefox Session
|
||||
|
||||
To close RDM, we follow a similar process to the one from opening RDM so we can
|
||||
restore the content back to a normal tab.
|
||||
|
||||
1. Create a temporary, hidden tab to hold the content.
|
||||
2. Mark the content tab browser's docshell as active so the frame is created
|
||||
1. Stop the tunnel between outer and inner browsers.
|
||||
2. Create a temporary, hidden tab to hold the content.
|
||||
3. Mark the content tab browser's docshell as active so the frame is created
|
||||
eagerly and will be ready to swap.
|
||||
3. Swap tab content from the browser within the viewport in the tool UI to the
|
||||
4. Swap tab content from the browser within the viewport in the tool UI to the
|
||||
regular browser tab, preserving all state via
|
||||
`gBrowser._swapBrowserDocShells`.
|
||||
4. Force the original browser tab to be remote since web content is loaded in
|
||||
5. Force the original browser tab to be remote since web content is loaded in
|
||||
the child process, and we're about to swap the content into this tab.
|
||||
5. Swap the content into the original browser tab and close the temporary tab
|
||||
6. Swap the content into the original browser tab and close the temporary tab
|
||||
used to hold the content via `swapBrowsersAndCloseOther`.
|
||||
|
||||
## Session Restore
|
||||
|
@ -1638,11 +1638,14 @@ RootClient.prototype = {
|
||||
if (browser.frameLoader.tabParent) {
|
||||
// Tabs in child process
|
||||
packet.tabId = browser.frameLoader.tabParent.tabId;
|
||||
} else if (browser.outerWindowID) {
|
||||
// <xul:browser> tabs in parent process
|
||||
packet.outerWindowID = browser.outerWindowID;
|
||||
} else {
|
||||
// Tabs in parent process
|
||||
// <iframe mozbrowser> tabs in parent process
|
||||
let windowUtils = browser.contentWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
packet.outerWindowID = windowUtils.outerWindowID;
|
||||
}
|
||||
} else {
|
||||
|
@ -117,13 +117,6 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Notification API V2 -->
|
||||
<intent-filter>
|
||||
<action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
|
||||
<data android:scheme="moz-notification" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="org.mozilla.gecko.UPDATE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@ -247,6 +240,17 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="org.mozilla.gecko.NotificationReceiver"
|
||||
android:exported="false">
|
||||
<!-- Notification API V2 -->
|
||||
<intent-filter>
|
||||
<action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
|
||||
<data android:scheme="moz-notification" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
|
||||
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
|
||||
#include ../search/manifests/SearchAndroidManifest_activities.xml.in
|
||||
|
@ -24,6 +24,7 @@ import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.delegates.BrowserAppDelegate;
|
||||
import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
|
||||
import org.mozilla.gecko.delegates.ScreenshotDelegate;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.distribution.DistributionStoreCallback;
|
||||
@ -312,7 +313,8 @@ public class BrowserApp extends GeckoApp
|
||||
(BrowserAppDelegate) new ReaderViewBookmarkPromotion(),
|
||||
(BrowserAppDelegate) new ContentNotificationsDelegate(),
|
||||
(BrowserAppDelegate) new PostUpdateHandler(),
|
||||
new TelemetryCorePingDelegate()
|
||||
new TelemetryCorePingDelegate(),
|
||||
new OfflineTabStatusDelegate()
|
||||
));
|
||||
|
||||
@NonNull
|
||||
|
@ -1630,8 +1630,6 @@ public abstract class GeckoApp
|
||||
|
||||
if (ACTION_ALERT_CALLBACK.equals(action)) {
|
||||
processAlertCallback(intent);
|
||||
} else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
|
||||
NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import org.mozilla.gecko.mozglue.SafeIntent;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
@ -45,7 +46,6 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
private static final String WHEN_ATTR = "when";
|
||||
private static final String PRIORITY_ATTR = "priority";
|
||||
private static final String LARGE_ICON_ATTR = "largeIcon";
|
||||
private static final String EVENT_TYPE_ATTR = "eventType";
|
||||
private static final String ACTIONS_ATTR = "actions";
|
||||
private static final String ACTION_ID_ATTR = "buttonId";
|
||||
private static final String ACTION_TITLE_ATTR = "title";
|
||||
@ -53,13 +53,16 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
private static final String PERSISTENT_ATTR = "persistent";
|
||||
private static final String HANDLER_ATTR = "handlerKey";
|
||||
private static final String COOKIE_ATTR = "cookie";
|
||||
static final String EVENT_TYPE_ATTR = "eventType";
|
||||
|
||||
private static final String NOTIFICATION_SCHEME = "moz-notification";
|
||||
|
||||
private static final String BUTTON_EVENT = "notification-button-clicked";
|
||||
private static final String CLICK_EVENT = "notification-clicked";
|
||||
private static final String CLEARED_EVENT = "notification-cleared";
|
||||
private static final String CLOSED_EVENT = "notification-closed";
|
||||
static final String CLEARED_EVENT = "notification-cleared";
|
||||
|
||||
static final String ORIGINAL_EXTRA_COMPONENT = "originalComponent";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@ -107,40 +110,17 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
return i.getBooleanExtra(HELPER_NOTIFICATION, false);
|
||||
}
|
||||
|
||||
public void handleNotificationIntent(SafeIntent i) {
|
||||
final Uri data = i.getData();
|
||||
if (data == null) {
|
||||
Log.e(LOGTAG, "handleNotificationEvent: empty data");
|
||||
return;
|
||||
}
|
||||
final String id = data.getQueryParameter(ID_ATTR);
|
||||
public static void getArgsAndSendNotificationIntent(SafeIntent intent) {
|
||||
final JSONObject args = new JSONObject();
|
||||
final Uri data = intent.getData();
|
||||
|
||||
final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
|
||||
if (id == null || notificationType == null) {
|
||||
Log.e(LOGTAG, "handleNotificationEvent: invalid intent parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the user swiped out the notification, we empty the id set.
|
||||
if (CLEARED_EVENT.equals(notificationType)) {
|
||||
mClearableNotifications.remove(id);
|
||||
// If Gecko isn't running, we throw away events where the notification was cancelled.
|
||||
// i.e. Don't bug the user if they're just closing a bunch of notifications.
|
||||
if (!GeckoThread.isRunning()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject args = new JSONObject();
|
||||
|
||||
// The handler and cookie parameters are optional.
|
||||
final String handler = data.getQueryParameter(HANDLER_ATTR);
|
||||
final String cookie = i.getStringExtra(COOKIE_ATTR);
|
||||
|
||||
try {
|
||||
args.put(ID_ATTR, id);
|
||||
args.put(ID_ATTR, data.getQueryParameter(ID_ATTR));
|
||||
args.put(EVENT_TYPE_ATTR, notificationType);
|
||||
args.put(HANDLER_ATTR, handler);
|
||||
args.put(COOKIE_ATTR, cookie);
|
||||
args.put(HANDLER_ATTR, data.getQueryParameter(HANDLER_ATTR));
|
||||
args.put(COOKIE_ATTR, data.getQueryParameter(COOKIE_ATTR));
|
||||
|
||||
if (BUTTON_EVENT.equals(notificationType)) {
|
||||
final String actionName = data.getQueryParameter(ACTION_ID_ATTR);
|
||||
@ -152,14 +132,28 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error building JSON notification arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleNotificationIntent(SafeIntent i) {
|
||||
final Uri data = i.getData();
|
||||
final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
|
||||
final String id = data.getQueryParameter(ID_ATTR);
|
||||
if (id == null || notificationType == null) {
|
||||
Log.e(LOGTAG, "handleNotificationEvent: invalid intent parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
getArgsAndSendNotificationIntent(i);
|
||||
|
||||
// If the notification was clicked, we are closing it. This must be executed after
|
||||
// sending the event to js side because when the notification is canceled no event can be
|
||||
// handled.
|
||||
if (CLICK_EVENT.equals(notificationType) && !i.getBooleanExtra(ONGOING_ATTR, false)) {
|
||||
// The handler and cookie parameters are optional.
|
||||
final String handler = data.getQueryParameter(HANDLER_ATTR);
|
||||
final String cookie = i.getStringExtra(COOKIE_ATTR);
|
||||
hideNotification(id, handler, cookie);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Uri.Builder getNotificationBuilder(JSONObject message, String type) {
|
||||
@ -192,15 +186,18 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
notificationIntent.setData(dataUri);
|
||||
notificationIntent.putExtra(HELPER_NOTIFICATION, true);
|
||||
notificationIntent.putExtra(COOKIE_ATTR, message.optString(COOKIE_ATTR));
|
||||
notificationIntent.setClass(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
|
||||
|
||||
// All intents get routed through the notificationReceiver. That lets us bail if we don't want to start Gecko
|
||||
final ComponentName name = new ComponentName(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
|
||||
notificationIntent.putExtra(ORIGINAL_EXTRA_COMPONENT, name);
|
||||
|
||||
return notificationIntent;
|
||||
}
|
||||
|
||||
private PendingIntent buildNotificationPendingIntent(JSONObject message, String type) {
|
||||
Uri.Builder builder = getNotificationBuilder(message, type);
|
||||
final Intent notificationIntent = buildNotificationIntent(message, builder);
|
||||
PendingIntent pi = PendingIntent.getActivity(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return pi;
|
||||
return PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent buildButtonClickPendingIntent(JSONObject message, JSONObject action) {
|
||||
|
@ -0,0 +1,56 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.mozglue.SafeIntent;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Broadcast receiver for Notifications. Will forward them to GeckoApp (and start Gecko) if they're clicked.
|
||||
* If they're being dismissed, it will not start Gecko, but may forward them to JS if Gecko is running.
|
||||
* This is also the only entry point for notification intents.
|
||||
*/
|
||||
public class NotificationReceiver extends BroadcastReceiver {
|
||||
private static final String LOGTAG = "Gecko" + NotificationReceiver.class.getSimpleName();
|
||||
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final Uri data = intent.getData();
|
||||
if (data == null) {
|
||||
Log.e(LOGTAG, "handleNotificationEvent: empty data");
|
||||
return;
|
||||
}
|
||||
|
||||
final String notificationType = data.getQueryParameter(NotificationHelper.EVENT_TYPE_ATTR);
|
||||
if (notificationType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the user swiped out the notification, we empty the id set.
|
||||
if (NotificationHelper.CLEARED_EVENT.equals(notificationType)) {
|
||||
// If Gecko isn't running, we throw away events where the notification was cancelled.
|
||||
// i.e. Don't bug the user if they're just closing a bunch of notifications.
|
||||
if (GeckoThread.isRunning()) {
|
||||
NotificationHelper.getArgsAndSendNotificationIntent(new SafeIntent(intent));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
forwardMessageToActivity(intent, context);
|
||||
}
|
||||
|
||||
private void forwardMessageToActivity(final Intent intent, final Context context) {
|
||||
final ComponentName name = intent.getExtras().getParcelable(NotificationHelper.ORIGINAL_EXTRA_COMPONENT);
|
||||
intent.setComponent(name);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
@ -104,6 +104,9 @@ public class Tab {
|
||||
private boolean mIsEditing;
|
||||
private final TabEditingState mEditingState = new TabEditingState();
|
||||
|
||||
// Will be true when tab is loaded from cache while device was offline.
|
||||
private boolean mLoadedFromCache;
|
||||
|
||||
public static final int STATE_DELAYED = 0;
|
||||
public static final int STATE_LOADING = 1;
|
||||
public static final int STATE_SUCCESS = 2;
|
||||
@ -301,6 +304,10 @@ public class Tab {
|
||||
return mHasOpenSearch;
|
||||
}
|
||||
|
||||
public boolean hasLoadedFromCache() {
|
||||
return mLoadedFromCache;
|
||||
}
|
||||
|
||||
public SiteIdentity getSiteIdentity() {
|
||||
return mSiteIdentity;
|
||||
}
|
||||
@ -536,6 +543,10 @@ public class Tab {
|
||||
mHasOpenSearch = hasOpenSearch;
|
||||
}
|
||||
|
||||
public void setLoadedFromCache(boolean loadedFromCache) {
|
||||
mLoadedFromCache = loadedFromCache;
|
||||
}
|
||||
|
||||
public void updateIdentityData(JSONObject identityData) {
|
||||
mSiteIdentity.update(identityData);
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ public class Tabs implements GeckoEventListener {
|
||||
"Tab:Added",
|
||||
"Tab:Close",
|
||||
"Tab:Select",
|
||||
"Tab:LoadedFromCache",
|
||||
"Content:LocationChange",
|
||||
"Content:SecurityChange",
|
||||
"Content:StateChange",
|
||||
@ -505,8 +506,9 @@ public class Tabs implements GeckoEventListener {
|
||||
tab.handleContentLoaded();
|
||||
notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
|
||||
} else if (event.equals("Content:PageShow")) {
|
||||
notifyListeners(tab, TabEvents.PAGE_SHOW);
|
||||
tab.setLoadedFromCache(message.getBoolean("fromCache"));
|
||||
tab.updateUserRequested(message.getString("userRequested"));
|
||||
notifyListeners(tab, TabEvents.PAGE_SHOW);
|
||||
} else if (event.equals("DOMContentLoaded")) {
|
||||
tab.handleContentLoaded();
|
||||
String backgroundColor = message.getString("bgColor");
|
||||
|
@ -104,6 +104,9 @@ public interface TelemetryContract {
|
||||
// Stop holding a resource (reader, bookmark, etc) for viewing later.
|
||||
UNSAVE("unsave.1"),
|
||||
|
||||
// When the user performs actions on the in-content network error page.
|
||||
NETERROR("neterror.1"),
|
||||
|
||||
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
|
||||
_TEST1("_test_event_1.1"),
|
||||
_TEST2("_test_event_2.1"),
|
||||
|
@ -0,0 +1,110 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.delegates;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.SnackbarHelper;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Displays "Showing offline version" message when tabs are loaded from cache while offline.
|
||||
*/
|
||||
public class OfflineTabStatusDelegate extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
|
||||
private WeakReference<Activity> activityReference;
|
||||
private WeakHashMap<Tab, Void> tabsQueuedForOfflineSnackbar = new WeakHashMap<>();
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
|
||||
super.onCreate(browserApp, savedInstanceState);
|
||||
activityReference = new WeakReference<Activity>(browserApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(BrowserApp browserApp) {
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(BrowserApp browserApp) {
|
||||
Tabs.unregisterOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
public void onTabChanged(final Tab tab, Tabs.TabEvents event, String data) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore tabs loaded regularly.
|
||||
if (!tab.hasLoadedFromCache()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore tabs displaying about pages
|
||||
if (AboutPages.isAboutPage(tab.getURL())) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
// Show offline notification if tab is visible, or queue it for display later.
|
||||
case PAGE_SHOW:
|
||||
if (!isTabsTrayVisible() && Tabs.getInstance().isSelectedTab(tab)) {
|
||||
showLoadedOfflineSnackbar(activityReference.get());
|
||||
} else {
|
||||
tabsQueuedForOfflineSnackbar.put(tab, null);
|
||||
}
|
||||
break;
|
||||
// When tab is selected and offline notification was queued, display it if possible.
|
||||
// SELECTED event might also fire when we're on a TabStrip, so check first.
|
||||
case SELECTED:
|
||||
if (isTabsTrayVisible()) {
|
||||
break;
|
||||
}
|
||||
if (tabsQueuedForOfflineSnackbar.containsKey(tab)) {
|
||||
showLoadedOfflineSnackbar(activityReference.get());
|
||||
tabsQueuedForOfflineSnackbar.remove(tab);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the notification snackbar and logs a telemetry event.
|
||||
*
|
||||
* @param activity which will be used for displaying the snackbar.
|
||||
*/
|
||||
private static void showLoadedOfflineSnackbar(final Activity activity) {
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.NETERROR, TelemetryContract.Method.TOAST, "usecache");
|
||||
|
||||
SnackbarHelper.showSnackbarWithActionAndColors(
|
||||
activity,
|
||||
activity.getResources().getString(R.string.tab_offline_version),
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
null, null, null,
|
||||
ContextCompat.getColor(activity, R.color.link_blue),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.delegates;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.tabs.TabsPanel;
|
||||
|
||||
public abstract class TabsTrayVisibilityAwareDelegate extends BrowserAppDelegate {
|
||||
private boolean tabsTrayVisible;
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
|
||||
tabsTrayVisible = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onTabsTrayShown(BrowserApp browserApp, TabsPanel tabsPanel) {
|
||||
tabsTrayVisible = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onTabsTrayHidden(BrowserApp browserApp, TabsPanel tabsPanel) {
|
||||
tabsTrayVisible = false;
|
||||
}
|
||||
|
||||
protected boolean isTabsTrayVisible() {
|
||||
return tabsTrayVisible;
|
||||
}
|
||||
}
|
@ -24,9 +24,10 @@ public class DownloadContent {
|
||||
public @interface Type {}
|
||||
public static final String TYPE_ASSET_ARCHIVE = "asset-archive";
|
||||
|
||||
@StringDef({KIND_FONT})
|
||||
@StringDef({KIND_FONT, KIND_HYPHENATION_DICTIONARY})
|
||||
public @interface Kind {}
|
||||
public static final String KIND_FONT = "font";
|
||||
public static final String KIND_HYPHENATION_DICTIONARY = "hyphenation";
|
||||
|
||||
private final String id;
|
||||
private final String location;
|
||||
@ -126,6 +127,19 @@ public class DownloadContent {
|
||||
return KIND_FONT.equals(kind);
|
||||
}
|
||||
|
||||
public boolean isHyphenationDictionary() {
|
||||
return KIND_HYPHENATION_DICTIONARY.equals(kind);
|
||||
}
|
||||
|
||||
/**
|
||||
*Checks whether the content to be downloaded is a known content.
|
||||
*Currently it checks whether the type is "Asset Archive" and is of kind
|
||||
*"Font" or "Hyphenation Dictionary".
|
||||
*/
|
||||
public boolean isKnownContent() {
|
||||
return ((isFont() || isHyphenationDictionary()) && isAssetArchive());
|
||||
}
|
||||
|
||||
public boolean isAssetArchive() {
|
||||
return TYPE_ASSET_ARCHIVE.equals(type);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
@ -17,14 +18,13 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.delegates.BrowserAppDelegate;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.UrlAnnotations;
|
||||
import org.mozilla.gecko.tabs.TabsPanel;
|
||||
import org.mozilla.gecko.delegates.TabsTrayVisibilityAwareDelegate;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
@ -35,7 +35,7 @@ import ch.boye.httpclientandroidlib.util.TextUtils;
|
||||
/**
|
||||
* Promote "Add to home screen" if user visits website often.
|
||||
*/
|
||||
public class AddToHomeScreenPromotion extends BrowserAppDelegate implements Tabs.OnTabsChangedListener {
|
||||
public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
|
||||
private static class URLHistory {
|
||||
public final long visits;
|
||||
public final long lastVisit;
|
||||
@ -54,15 +54,15 @@ public class AddToHomeScreenPromotion extends BrowserAppDelegate implements Tabs
|
||||
|
||||
private WeakReference<Activity> activityReference;
|
||||
private boolean isEnabled;
|
||||
private boolean isInForeground;
|
||||
private int minimumVisits;
|
||||
private int lastVisitMinimumAgeMs;
|
||||
private int lastVisitMaximumAgeMs;
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
|
||||
super.onCreate(browserApp, savedInstanceState);
|
||||
activityReference = new WeakReference<Activity>(browserApp);
|
||||
isInForeground = true;
|
||||
|
||||
initializeExperiment(browserApp);
|
||||
}
|
||||
@ -77,16 +77,6 @@ public class AddToHomeScreenPromotion extends BrowserAppDelegate implements Tabs
|
||||
Tabs.unregisterOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabsTrayShown(BrowserApp browserApp, TabsPanel tabsPanel) {
|
||||
isInForeground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabsTrayHidden(BrowserApp browserApp, TabsPanel tabsPanel) {
|
||||
isInForeground = true;
|
||||
}
|
||||
|
||||
private void initializeExperiment(Context context) {
|
||||
if (!SwitchBoard.isInExperiment(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN)) {
|
||||
Log.v(LOGTAG, "Experiment not enabled");
|
||||
@ -139,7 +129,7 @@ public class AddToHomeScreenPromotion extends BrowserAppDelegate implements Tabs
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInForeground) {
|
||||
if (isTabsTrayVisible()) {
|
||||
// We only want to show this prompt if this tab is in the foreground and not on top
|
||||
// of the tabs tray.
|
||||
return;
|
||||
|
@ -47,6 +47,9 @@
|
||||
|
||||
<!ENTITY switch_to_tab "Switch to tab">
|
||||
|
||||
<!-- Localization note: Shown in a snackbar when tab is loaded from cache while device was offline. -->
|
||||
<!ENTITY tab_offline_version "Showing offline version">
|
||||
|
||||
<!ENTITY crash_reporter_title "&brandShortName; Crash Reporter">
|
||||
<!ENTITY crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
|
||||
<!ENTITY crash_send_report_message3 "Tell &vendorShortName; about this crash so they can fix it">
|
||||
|
@ -262,7 +262,9 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'delegates/BookmarkStateChangeDelegate.java',
|
||||
'delegates/BrowserAppDelegate.java',
|
||||
'delegates/BrowserAppDelegateWithReference.java',
|
||||
'delegates/OfflineTabStatusDelegate.java',
|
||||
'delegates/ScreenshotDelegate.java',
|
||||
'delegates/TabsTrayVisibilityAwareDelegate.java',
|
||||
'DevToolsAuthHelper.java',
|
||||
'distribution/Distribution.java',
|
||||
'distribution/DistributionStoreCallback.java',
|
||||
@ -479,6 +481,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'NotificationClient.java',
|
||||
'NotificationHandler.java',
|
||||
'NotificationHelper.java',
|
||||
'NotificationReceiver.java',
|
||||
'notifications/WhatsNewReceiver.java',
|
||||
'NotificationService.java',
|
||||
'NSSBridge.java',
|
||||
|
@ -68,6 +68,8 @@
|
||||
|
||||
<string name="switch_to_tab">&switch_to_tab;</string>
|
||||
|
||||
<string name="tab_offline_version">&tab_offline_version;</string>
|
||||
|
||||
<string name="crash_reporter_title">&crash_reporter_title;</string>
|
||||
<string name="crash_message2">&crash_message2;</string>
|
||||
<string name="crash_send_report_message3">&crash_send_report_message3;</string>
|
||||
|
@ -4297,7 +4297,8 @@ Tab.prototype = {
|
||||
Messaging.sendRequest({
|
||||
type: "Content:PageShow",
|
||||
tabID: this.id,
|
||||
userRequested: this.userRequested
|
||||
userRequested: this.userRequested,
|
||||
fromCache: Tabs.useCache
|
||||
});
|
||||
|
||||
this.isSearch = false;
|
||||
@ -7377,25 +7378,6 @@ var Tabs = {
|
||||
// Clear the domain cache whenever a page is loaded into any browser.
|
||||
this._domains.clear();
|
||||
|
||||
// Notify if we are loading a page from cache.
|
||||
if (this._useCache) {
|
||||
let targetDoc = aEvent.originalTarget;
|
||||
let isTopLevel = (targetDoc.defaultView.parent === targetDoc.defaultView);
|
||||
|
||||
// Ignore any about: pages, especially about:neterror since it means we failed to find the page in cache.
|
||||
let targetURI = targetDoc.documentURI;
|
||||
if (isTopLevel && !targetURI.startsWith("about:")) {
|
||||
UITelemetry.addEvent("neterror.1", "toast", null, "usecache");
|
||||
Snackbars.show(
|
||||
Strings.browser.GetStringFromName("networkOffline.message2"),
|
||||
Snackbars.LENGTH_INDEFINITE,
|
||||
{
|
||||
// link_blue
|
||||
backgroundColor: "#0096DD"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "TabOpen":
|
||||
// Use opening a new tab as a trigger to expire the most stale tab.
|
||||
|
@ -434,9 +434,6 @@ openInApp.pageAction = Open in App
|
||||
openInApp.ok = OK
|
||||
openInApp.cancel = Cancel
|
||||
|
||||
#Network Offline
|
||||
networkOffline.message2 = Showing offline version
|
||||
|
||||
#Tab sharing
|
||||
tabshare.title = "Choose a tab to stream"
|
||||
#Tabs in context menus
|
||||
|
@ -492,6 +492,56 @@ public class TestDownloadAction {
|
||||
verify(catalog, times(11)).rememberFailure(eq(content), anyInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: If the file to be downloaded is of kind - "hyphenation"
|
||||
*
|
||||
* Verify that:
|
||||
* * isHyphenationDictionary returns true for a download content with kind "hyphenation"
|
||||
* * isHyphenationDictionary returns false for a download content with unknown/different kind like "Font"
|
||||
*/
|
||||
@Test
|
||||
public void testIsHyphenationDictionary() throws Exception {
|
||||
DownloadContent hyphenationContent = createHyphenationDictionary();
|
||||
Assert.assertTrue(hyphenationContent.isHyphenationDictionary());
|
||||
DownloadContent fontContent = createFont();
|
||||
Assert.assertFalse(fontContent.isHyphenationDictionary());
|
||||
DownloadContent unknownContent = createUnknownContent(1024L);
|
||||
Assert.assertFalse(unknownContent.isHyphenationDictionary());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: If the content to be downloaded is known
|
||||
*
|
||||
* Verify that:
|
||||
* * isKnownContent returns true for a downloadable content with a known kind and type.
|
||||
* * isKnownContent returns false for a downloadable content with unknown kind and type.
|
||||
*/
|
||||
@Test
|
||||
public void testIsKnownContent() throws Exception {
|
||||
DownloadContent fontContent = createFontWithSize(1024L);
|
||||
DownloadContent hyphenationContent = createHyphenationDictionaryWithSize(1024L);
|
||||
DownloadContent unknownContent = createUnknownContent(1024L);
|
||||
DownloadContent contentWithUnknownType = createContentWithoutType(1024L);
|
||||
|
||||
Assert.assertTrue(fontContent.isKnownContent());
|
||||
Assert.assertTrue(hyphenationContent.isKnownContent());
|
||||
Assert.assertFalse(unknownContent.isKnownContent());
|
||||
Assert.assertFalse(contentWithUnknownType.isKnownContent());
|
||||
}
|
||||
|
||||
private DownloadContent createUnknownContent(long size) {
|
||||
return new DownloadContentBuilder()
|
||||
.setSize(size)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DownloadContent createContentWithoutType(long size) {
|
||||
return new DownloadContentBuilder()
|
||||
.setKind(DownloadContent.KIND_HYPHENATION_DICTIONARY)
|
||||
.setSize(size)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DownloadContent createFont() {
|
||||
return createFontWithSize(102400L);
|
||||
}
|
||||
@ -504,6 +554,18 @@ public class TestDownloadAction {
|
||||
.build();
|
||||
}
|
||||
|
||||
private DownloadContent createHyphenationDictionary() {
|
||||
return createHyphenationDictionaryWithSize(102400L);
|
||||
}
|
||||
|
||||
private DownloadContent createHyphenationDictionaryWithSize(long size) {
|
||||
return new DownloadContentBuilder()
|
||||
.setKind(DownloadContent.KIND_HYPHENATION_DICTIONARY)
|
||||
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
|
||||
.setSize(size)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DownloadContentCatalog mockCatalogWithScheduledDownloads(DownloadContent... content) {
|
||||
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
|
||||
doReturn(Arrays.asList(content)).when(catalog).getScheduledDownloads();
|
||||
|
@ -485,6 +485,7 @@ class JsapiTestsCommand(MachCommandBase):
|
||||
return jsapi_tests_result
|
||||
|
||||
def autotry_parser():
|
||||
print("mach try is under development, please file bugs blocking 1149670.")
|
||||
from autotry import arg_parser
|
||||
return arg_parser()
|
||||
|
||||
@ -614,8 +615,6 @@ class PushToTry(MachCommandBase):
|
||||
from mozbuild.testing import TestResolver
|
||||
from autotry import AutoTry
|
||||
|
||||
print("mach try is under development, please file bugs blocking 1149670.")
|
||||
|
||||
resolver_func = lambda: self._spawn(TestResolver)
|
||||
at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=549539
|
||||
// https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661
|
||||
// https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3
|
||||
// http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240
|
||||
// https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SpecialPowersObserver", "SpecialPowersObserverFactory"];
|
||||
|
@ -9,16 +9,29 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
import which
|
||||
import difflib
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import ConfigParser
|
||||
|
||||
def validate_choices(values, choices):
|
||||
valid = True
|
||||
for value in values:
|
||||
if value not in choices:
|
||||
print 'Invalid choice {v!r}. Allowed choices: {c!r}'.format(v=value, c=choices)
|
||||
sys.exit(1)
|
||||
corrections = difflib.get_close_matches(value, choices)
|
||||
if len(corrections) == 0:
|
||||
print 'Potentially invalid choice {v!r}. Neither the requested value nor a similar value was found.'.format(v=value)
|
||||
print 'List of possible values: {c!r}'.format(c=choices)
|
||||
result = raw_input('Are you sure you want to continue? [y/N] ').strip()
|
||||
if not 'y' in result.lower():
|
||||
valid = False
|
||||
elif corrections[0] == value:
|
||||
continue
|
||||
else:
|
||||
valid = False
|
||||
print 'Invalid choice {v!r}. Some suggestions (limit three): {c!r}?'.format(v=value, c=corrections)
|
||||
if not valid:
|
||||
sys.exit(1)
|
||||
|
||||
class ValidatePlatforms(argparse.Action):
|
||||
def __call__(self, parser, args, values, option_string=None):
|
||||
|
@ -1,75 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
.console-box {
|
||||
-moz-binding: url("chrome://global/content/consoleBindings.xml#console-box");
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.console-rows {
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
.console-row[type="error"],
|
||||
.console-row[type="warning"],
|
||||
.console-row[type="message"][typetext] {
|
||||
-moz-binding: url("chrome://global/content/consoleBindings.xml#error");
|
||||
}
|
||||
|
||||
.console-row[type="message"] {
|
||||
-moz-binding: url("chrome://global/content/consoleBindings.xml#message");
|
||||
}
|
||||
|
||||
.console-msg-text,
|
||||
.console-error-msg {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.console-error-source {
|
||||
-moz-binding: url("chrome://global/content/consoleBindings.xml#console-error-source");
|
||||
}
|
||||
|
||||
.console-dots {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* :::::::::: hiding and showing of rows for each mode :::::::::: */
|
||||
|
||||
.console-box[mode="Warnings"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="error"],
|
||||
.console-box[mode="Messages"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="error"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-box[mode="Errors"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="warning"],
|
||||
.console-box[mode="Messages"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="warning"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-box[mode="Errors"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="message"],
|
||||
.console-box[mode="Warnings"] > .console-box-internal > .console-rows
|
||||
> .console-row[type="message"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filtered-by-string {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* If line number is 0, hide the line number section */
|
||||
.lineNumberRow[line="0"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#TextboxEval {
|
||||
direction: ltr;
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gConsole, gConsoleBundle, gTextBoxEval, gEvaluator, gCodeToEvaluate;
|
||||
var gFilter;
|
||||
|
||||
/* :::::::: Console Initialization ::::::::::::::: */
|
||||
|
||||
window.onload = function()
|
||||
{
|
||||
gConsole = document.getElementById("ConsoleBox");
|
||||
gConsoleBundle = document.getElementById("ConsoleBundle");
|
||||
gTextBoxEval = document.getElementById("TextboxEval");
|
||||
gEvaluator = document.getElementById("Evaluator");
|
||||
gFilter = document.getElementById("Filter");
|
||||
|
||||
updateSortCommand(gConsole.sortOrder);
|
||||
updateModeCommand(gConsole.mode);
|
||||
|
||||
gEvaluator.addEventListener("load", loadOrDisplayResult, true);
|
||||
}
|
||||
|
||||
/* :::::::: Console UI Functions ::::::::::::::: */
|
||||
|
||||
function changeFilter()
|
||||
{
|
||||
gConsole.filter = gFilter.value;
|
||||
|
||||
document.persist("ConsoleBox", "filter");
|
||||
}
|
||||
|
||||
function changeMode(aMode)
|
||||
{
|
||||
switch (aMode) {
|
||||
case "Errors":
|
||||
case "Warnings":
|
||||
case "Messages":
|
||||
gConsole.mode = aMode;
|
||||
break;
|
||||
case "All":
|
||||
gConsole.mode = null;
|
||||
}
|
||||
|
||||
document.persist("ConsoleBox", "mode");
|
||||
}
|
||||
|
||||
function clearConsole()
|
||||
{
|
||||
gConsole.clear();
|
||||
}
|
||||
|
||||
function changeSortOrder(aOrder)
|
||||
{
|
||||
updateSortCommand(gConsole.sortOrder = aOrder);
|
||||
}
|
||||
|
||||
function updateSortCommand(aOrder)
|
||||
{
|
||||
var orderString = aOrder == 'reverse' ? "Descend" : "Ascend";
|
||||
var bc = document.getElementById("Console:sort"+orderString);
|
||||
bc.setAttribute("checked", true);
|
||||
|
||||
orderString = aOrder == 'reverse' ? "Ascend" : "Descend";
|
||||
bc = document.getElementById("Console:sort"+orderString);
|
||||
bc.setAttribute("checked", false);
|
||||
}
|
||||
|
||||
function updateModeCommand(aMode)
|
||||
{
|
||||
/* aMode can end up invalid if it set by an extension that replaces */
|
||||
/* mode and then it is uninstalled or disabled */
|
||||
var bc = document.getElementById("Console:mode" + aMode) ||
|
||||
document.getElementById("Console:modeAll");
|
||||
bc.setAttribute("checked", true);
|
||||
}
|
||||
|
||||
function onEvalKeyPress(aEvent)
|
||||
{
|
||||
if (aEvent.keyCode == 13)
|
||||
evaluateTypein();
|
||||
}
|
||||
|
||||
function evaluateTypein()
|
||||
{
|
||||
gCodeToEvaluate = gTextBoxEval.value;
|
||||
// reset the iframe first; the code will be evaluated in loadOrDisplayResult
|
||||
// below, once about:blank has completed loading (see bug 385092)
|
||||
gEvaluator.contentWindow.location = "about:blank";
|
||||
}
|
||||
|
||||
function loadOrDisplayResult()
|
||||
{
|
||||
if (gCodeToEvaluate) {
|
||||
gEvaluator.contentWindow.location = "javascript: " +
|
||||
gCodeToEvaluate.replace(/%/g, "%25");
|
||||
gCodeToEvaluate = "";
|
||||
return;
|
||||
}
|
||||
|
||||
var resultRange = gEvaluator.contentDocument.createRange();
|
||||
resultRange.selectNode(gEvaluator.contentDocument.documentElement);
|
||||
var result = resultRange.toString();
|
||||
if (result)
|
||||
Services.console.logStringMessage(result);
|
||||
// or could use appendMessage which doesn't persist
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
<?xml version="1.0"?> <!-- -*- tab-width: 4; indent-tabs-mode: nil -*- -->
|
||||
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/console/console.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://global/content/console.css" type="text/css"?>
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % console SYSTEM "chrome://global/locale/console.dtd"> %console;
|
||||
]>
|
||||
|
||||
<window id="JSConsoleWindow"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&errorConsole.title;"
|
||||
windowtype="global:console"
|
||||
width="640" height="480"
|
||||
screenX="10" screenY="10"
|
||||
persist="screenX screenY width height sizemode"
|
||||
onclose="return closeWindow(false);">
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript" src="chrome://global/content/console.js"/>
|
||||
<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
|
||||
|
||||
<stringbundle id="ConsoleBundle" src="chrome://global/locale/console.properties"/>
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<commandset id="consoleCommands">
|
||||
<command id="cmd_close" oncommand="closeWindow(true)"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="consoleKeys">
|
||||
<key id="key_close" key="&closeCmd.commandkey;" modifiers="accel"
|
||||
command="cmd_close"/>
|
||||
<key id="key_close2" keycode="VK_ESCAPE" command="cmd_close"/>
|
||||
<key id="key_focus1" key="&focus1.commandkey;" modifiers="accel"
|
||||
oncommand="gTextBoxEval.focus()"/>
|
||||
<key id="key_focus2" key="&focus2.commandkey;" modifiers="alt"
|
||||
oncommand="gTextBoxEval.focus()"/>
|
||||
</keyset>
|
||||
|
||||
<popupset id="ContextMenus">
|
||||
<menupopup id="ConsoleContext">
|
||||
<menuitem type="radio" id="Console:sortAscend"
|
||||
label="&sortFirst.label;" accesskey="&sortFirst.accesskey;"
|
||||
oncommand="changeSortOrder('forward');"/>
|
||||
<menuitem type="radio" id="Console:sortDescend"
|
||||
label="&sortLast.label;" accesskey="&sortLast.accesskey;"
|
||||
oncommand="changeSortOrder('reverse');"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_copy_cm" command="cmd_copy"
|
||||
label="©Cmd.label;" accesskey="©Cmd.accesskey;"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<toolbox id="console-toolbox">
|
||||
<toolbar class="chromeclass-toolbar" id="ToolbarMode">
|
||||
<hbox id="viewGroup">
|
||||
<toolbarbutton type="radio" group="mode" id="Console:modeAll"
|
||||
label="&all.label;" accesskey="&all.accesskey;"
|
||||
oncommand="changeMode('All');"/>
|
||||
<toolbarbutton type="radio" group="mode" id="Console:modeErrors"
|
||||
label="&errors.label;" accesskey="&errors.accesskey;"
|
||||
oncommand="changeMode('Errors');"/>
|
||||
<toolbarbutton type="radio" group="mode" id="Console:modeWarnings"
|
||||
label="&warnings.label;" accesskey="&warnings.accesskey;"
|
||||
oncommand="changeMode('Warnings');"/>
|
||||
<toolbarbutton type="radio" group="mode" id="Console:modeMessages"
|
||||
label="&messages.label;" accesskey="&messages.accesskey;"
|
||||
oncommand="changeMode('Messages');"/>
|
||||
</hbox>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="Console:clear" oncommand="clearConsole();"
|
||||
label="&clear.label;" accesskey="&clear.accesskey;"/>
|
||||
</toolbar>
|
||||
|
||||
<toolbar class="chromeclass-toolbar" id="ToolbarEval" align="center" nowindowdrag="true">
|
||||
<label value="&codeEval.label;" accesskey="&codeEval.accesskey;" control="TextboxEval"/>
|
||||
<textbox id="TextboxEval" class="toolbar" value="" onkeypress="onEvalKeyPress(event)" flex="1"/>
|
||||
<toolbarbutton id="ButtonEval" label="&evaluate.label;"
|
||||
accesskey="&evaluate.accesskey;" oncommand="evaluateTypein()"/>
|
||||
</toolbar>
|
||||
</toolbox>
|
||||
|
||||
<vbox id="ConsoleBox" class="console-box" flex="1" context="ConsoleContext" persist="sortOrder"/>
|
||||
|
||||
<iframe name="Evaluator" id="Evaluator" collapsed="true"/>
|
||||
|
||||
<statusbar>
|
||||
<statusbarpanel flex="1" pack="start">
|
||||
<label value="&filter2.label;" control="Filter"/>
|
||||
<textbox accesskey="&filter2.accesskey;" type="search"
|
||||
id="Filter" oncommand="changeFilter();"/>
|
||||
</statusbarpanel>
|
||||
</statusbar>
|
||||
</window>
|
@ -1,547 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings SYSTEM "chrome://global/locale/console.dtd">
|
||||
|
||||
<bindings id="consoleBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="console-box" extends="xul:box">
|
||||
<content>
|
||||
<xul:stringbundle src="chrome://global/locale/console.properties" role="string-bundle"/>
|
||||
<xul:vbox class="console-box-internal">
|
||||
<xul:vbox class="console-rows" role="console-rows" xbl:inherits="dir=sortOrder"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<field name="limit" readonly="true">
|
||||
250
|
||||
</field>
|
||||
|
||||
<field name="fieldMaxLength" readonly="true">
|
||||
<!-- Limit displayed string lengths to avoid performance issues. (Bug 796179 and 831020) -->
|
||||
200
|
||||
</field>
|
||||
|
||||
<field name="showChromeErrors" readonly="true">
|
||||
Services.prefs.getBoolPref("javascript.options.showInConsole");
|
||||
</field>
|
||||
|
||||
<property name="count" readonly="true">
|
||||
<getter>return this.mCount</getter>
|
||||
</property>
|
||||
|
||||
<property name="mode">
|
||||
<getter>return this.mMode;</getter>
|
||||
<setter><![CDATA[
|
||||
if (this.mode != val) {
|
||||
this.mMode = val || "All";
|
||||
this.setAttribute("mode", this.mMode);
|
||||
this.selectedItem = null;
|
||||
}
|
||||
return val;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="filter">
|
||||
<getter>return this.mFilter;</getter>
|
||||
<setter><![CDATA[
|
||||
val = val.toLowerCase();
|
||||
if (this.mFilter != val) {
|
||||
this.mFilter = val;
|
||||
for (let aRow of this.mConsoleRowBox.children) {
|
||||
this.filterElement(aRow);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="sortOrder">
|
||||
<getter>return this.getAttribute("sortOrder");</getter>
|
||||
<setter>this.setAttribute("sortOrder", val); return val;</setter>
|
||||
</property>
|
||||
<field name="mSelectedItem">null</field>
|
||||
<property name="selectedItem">
|
||||
<getter>return this.mSelectedItem</getter>
|
||||
<setter><![CDATA[
|
||||
if (this.mSelectedItem)
|
||||
this.mSelectedItem.removeAttribute("selected");
|
||||
|
||||
this.mSelectedItem = val;
|
||||
if (val)
|
||||
val.setAttribute("selected", "true");
|
||||
|
||||
// Update edit commands
|
||||
window.updateCommands("focus");
|
||||
return val;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<method name="init">
|
||||
<body><![CDATA[
|
||||
this.mCount = 0;
|
||||
|
||||
this.mConsoleListener = {
|
||||
console: this,
|
||||
observe : function(aObject) {
|
||||
// The message can arrive a little bit after the xbl binding has been
|
||||
// unbind. So node.appendItem will not be available anymore.
|
||||
if ('appendItem' in this.console)
|
||||
this.console.appendItem(aObject);
|
||||
}
|
||||
};
|
||||
|
||||
this.mConsoleRowBox = document.getAnonymousElementByAttribute(this, "role", "console-rows");
|
||||
this.mStrBundle = document.getAnonymousElementByAttribute(this, "role", "string-bundle");
|
||||
|
||||
try {
|
||||
Services.console.registerListener(this.mConsoleListener);
|
||||
} catch (ex) {
|
||||
appendItem(
|
||||
"Unable to display errors - couldn't get Console Service component. " +
|
||||
"(Missing @mozilla.org/consoleservice;1)");
|
||||
return;
|
||||
}
|
||||
|
||||
this.mMode = this.getAttribute("mode") || "All";
|
||||
this.mFilter = "";
|
||||
|
||||
this.appendInitialItems();
|
||||
window.controllers.insertControllerAt(0, this._controller);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="destroy">
|
||||
<body><![CDATA[
|
||||
Services.console.unregisterListener(this.mConsoleListener);
|
||||
window.controllers.removeController(this._controller);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="appendInitialItems">
|
||||
<body><![CDATA[
|
||||
var messages = Services.console.getMessageArray();
|
||||
|
||||
// In case getMessageArray returns 0-length array as null
|
||||
if (!messages)
|
||||
messages = [];
|
||||
|
||||
var limit = messages.length - this.limit;
|
||||
if (limit < 0) limit = 0;
|
||||
|
||||
// Checks if console ever been cleared
|
||||
for (var i = messages.length - 1; i >= limit; --i)
|
||||
if (!messages[i].message)
|
||||
break;
|
||||
|
||||
// Populate with messages after latest "clear"
|
||||
while (++i < messages.length)
|
||||
this.appendItem(messages[i]);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="appendItem">
|
||||
<parameter name="aObject"/>
|
||||
<body><![CDATA[
|
||||
try {
|
||||
// Try to QI it to a script error to get more info
|
||||
var scriptError = aObject.QueryInterface(Components.interfaces.nsIScriptError);
|
||||
|
||||
// filter chrome urls
|
||||
if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
|
||||
return;
|
||||
|
||||
// filter private windows
|
||||
if (scriptError.isFromPrivateWindow)
|
||||
return;
|
||||
|
||||
this.appendError(scriptError);
|
||||
} catch (ex) {
|
||||
try {
|
||||
// Try to QI it to a console message
|
||||
var msg = aObject.QueryInterface(Components.interfaces.nsIConsoleMessage);
|
||||
if (msg.message)
|
||||
this.appendMessage(msg.message);
|
||||
else // observed a null/"clear" message
|
||||
this.clearConsole();
|
||||
} catch (ex2) {
|
||||
// Give up and append the object itself as a string
|
||||
this.appendMessage(aObject);
|
||||
}
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_truncateIfNecessary">
|
||||
<parameter name="aString"/>
|
||||
<parameter name="aMiddleCharacter"/>
|
||||
<body><![CDATA[
|
||||
if (!aString || aString.length <= this.fieldMaxLength)
|
||||
return {string: aString, column: aMiddleCharacter};
|
||||
let halfLimit = this.fieldMaxLength / 2;
|
||||
if (!aMiddleCharacter || aMiddleCharacter < 0 || aMiddleCharacter > aString.length)
|
||||
aMiddleCharacter = halfLimit;
|
||||
|
||||
let startPosition = 0;
|
||||
let endPosition = aString.length;
|
||||
if (aMiddleCharacter - halfLimit >= 0)
|
||||
startPosition = aMiddleCharacter - halfLimit;
|
||||
if (aMiddleCharacter + halfLimit <= aString.length)
|
||||
endPosition = aMiddleCharacter + halfLimit;
|
||||
if (endPosition - startPosition < this.fieldMaxLength)
|
||||
endPosition += this.fieldMaxLength - (endPosition - startPosition);
|
||||
let truncatedString = aString.substring(startPosition, endPosition);
|
||||
let Ci = Components.interfaces;
|
||||
let ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
if (startPosition > 0) {
|
||||
truncatedString = ellipsis + truncatedString;
|
||||
aMiddleCharacter += ellipsis.length;
|
||||
}
|
||||
if (endPosition < aString.length)
|
||||
truncatedString = truncatedString + ellipsis;
|
||||
|
||||
return {
|
||||
string: truncatedString,
|
||||
column: aMiddleCharacter - startPosition
|
||||
};
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="appendError">
|
||||
<parameter name="aObject"/>
|
||||
<body><![CDATA[
|
||||
var row = this.createConsoleRow();
|
||||
var nsIScriptError = Components.interfaces.nsIScriptError;
|
||||
|
||||
// nsIConsoleMessage constants: debug, info, warn, error
|
||||
var typetext = ["typeMessage", "typeMessage", "typeWarning", "typeError"][aObject.logLevel];
|
||||
var type = ["message", "message", "warning", "error"][aObject.logLevel];
|
||||
|
||||
row.setAttribute("typetext", this.mStrBundle.getString(typetext));
|
||||
row.setAttribute("type", type);
|
||||
row.setAttribute("msg", aObject.errorMessage);
|
||||
row.setAttribute("category", aObject.category);
|
||||
row.setAttribute("time", this.properFormatTime(aObject.timeStamp));
|
||||
if (aObject.lineNumber || aObject.sourceName) {
|
||||
row.setAttribute("href", this._truncateIfNecessary(aObject.sourceName).string);
|
||||
row.mSourceName = aObject.sourceName;
|
||||
row.setAttribute("line", aObject.lineNumber);
|
||||
} else {
|
||||
row.setAttribute("hideSource", "true");
|
||||
}
|
||||
if (aObject.sourceLine) {
|
||||
let sourceLine = aObject.sourceLine.replace(/\s/g, " ");
|
||||
let truncatedLineObj = this._truncateIfNecessary(sourceLine, aObject.columnNumber);
|
||||
row.setAttribute("code", truncatedLineObj.string);
|
||||
row.mSourceLine = sourceLine;
|
||||
if (aObject.columnNumber) {
|
||||
row.setAttribute("col", aObject.columnNumber);
|
||||
row.setAttribute("errorDots", this.repeatChar(" ", truncatedLineObj.column));
|
||||
row.setAttribute("errorCaret", " ");
|
||||
} else {
|
||||
row.setAttribute("hideCaret", "true");
|
||||
}
|
||||
} else {
|
||||
row.setAttribute("hideCode", "true");
|
||||
}
|
||||
|
||||
this.appendConsoleRow(row);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="appendMessage">
|
||||
<parameter name="aMessage"/>
|
||||
<parameter name="aType"/>
|
||||
<body><![CDATA[
|
||||
var row = this.createConsoleRow();
|
||||
row.setAttribute("type", aType || "message");
|
||||
row.setAttribute("msg", aMessage);
|
||||
this.appendConsoleRow(row);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="clear">
|
||||
<body><![CDATA[
|
||||
// add a "clear" message (mainly for other listeners)
|
||||
Services.console.logStringMessage(null);
|
||||
Services.console.reset();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="properFormatTime">
|
||||
<parameter name="aTime"/>
|
||||
<body><![CDATA[
|
||||
const dateServ = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
|
||||
.getService(Components.interfaces.nsIScriptableDateFormat);
|
||||
let errorTime = new Date(aTime);
|
||||
return dateServ.FormatDateTime("", dateServ.dateFormatShort, dateServ.timeFormatSeconds,
|
||||
errorTime.getFullYear(), errorTime.getMonth() + 1, errorTime.getDate(),
|
||||
errorTime.getHours(), errorTime.getMinutes(), errorTime.getSeconds());
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="copySelectedItem">
|
||||
<body><![CDATA[
|
||||
if (this.mSelectedItem) try {
|
||||
const clipURI = "@mozilla.org/widget/clipboardhelper;1";
|
||||
const clipI = Components.interfaces.nsIClipboardHelper;
|
||||
var clipboard = Components.classes[clipURI].getService(clipI);
|
||||
|
||||
clipboard.copyString(this.mSelectedItem.toString());
|
||||
} catch (ex) {
|
||||
// Unable to copy anything, die quietly
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="createConsoleRow">
|
||||
<body><![CDATA[
|
||||
var row = document.createElement("box");
|
||||
row.setAttribute("class", "console-row");
|
||||
row._IsConsoleRow = true;
|
||||
row._ConsoleBox = this;
|
||||
return row;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="appendConsoleRow">
|
||||
<parameter name="aRow"/>
|
||||
<body><![CDATA[
|
||||
this.filterElement(aRow);
|
||||
this.mConsoleRowBox.appendChild(aRow);
|
||||
if (++this.mCount > this.limit) this.deleteFirst();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="deleteFirst">
|
||||
<body><![CDATA[
|
||||
var node = this.mConsoleRowBox.firstChild;
|
||||
this.mConsoleRowBox.removeChild(node);
|
||||
--this.mCount;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="clearConsole">
|
||||
<body><![CDATA[
|
||||
if (this.mCount == 0) // already clear
|
||||
return;
|
||||
this.mCount = 0;
|
||||
|
||||
var newRows = this.mConsoleRowBox.cloneNode(false);
|
||||
this.mConsoleRowBox.parentNode.replaceChild(newRows, this.mConsoleRowBox);
|
||||
this.mConsoleRowBox = newRows;
|
||||
this.selectedItem = null;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="filterElement">
|
||||
<parameter name="aRow" />
|
||||
<body><![CDATA[
|
||||
let anyMatch = ["msg", "line", "code"].some(function (key) {
|
||||
return (aRow.hasAttribute(key) &&
|
||||
this.stringMatchesFilters(aRow.getAttribute(key), this.mFilter));
|
||||
}, this) || (aRow.mSourceName &&
|
||||
this.stringMatchesFilters(aRow.mSourceName, this.mFilter));
|
||||
|
||||
if (anyMatch) {
|
||||
aRow.classList.remove("filtered-by-string")
|
||||
} else {
|
||||
aRow.classList.add("filtered-by-string")
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- UTILITY FUNCTIONS -->
|
||||
|
||||
<method name="repeatChar">
|
||||
<parameter name="aChar"/>
|
||||
<parameter name="aCol"/>
|
||||
<body><![CDATA[
|
||||
if (--aCol <= 0)
|
||||
return "";
|
||||
|
||||
for (var i = 2; i < aCol; i += i)
|
||||
aChar += aChar;
|
||||
|
||||
return aChar + aChar.slice(0, aCol - aChar.length);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="stringMatchesFilters">
|
||||
<parameter name="aString"/>
|
||||
<parameter name="aFilter"/>
|
||||
<body><![CDATA[
|
||||
if (!aString || !aFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let searchStr = aString.toLowerCase();
|
||||
let filterStrings = aFilter.split(/\s+/);
|
||||
return !filterStrings.some(function (f) {
|
||||
return searchStr.indexOf(f) == -1;
|
||||
});
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<constructor>this.init();</constructor>
|
||||
<destructor>this.destroy();</destructor>
|
||||
|
||||
<!-- Command controller for the copy command -->
|
||||
<field name="_controller"><![CDATA[({
|
||||
_outer: this,
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Components.interfaces.nsIController) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
},
|
||||
|
||||
supportsCommand: function(aCommand) {
|
||||
return aCommand == "cmd_copy";
|
||||
},
|
||||
|
||||
isCommandEnabled: function(aCommand) {
|
||||
return aCommand == "cmd_copy" && this._outer.selectedItem;
|
||||
},
|
||||
|
||||
doCommand: function(aCommand) {
|
||||
if (aCommand == "cmd_copy")
|
||||
this._outer.copySelectedItem();
|
||||
},
|
||||
|
||||
onEvent: function() { }
|
||||
});]]></field>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="mousedown"><![CDATA[
|
||||
if (event.button == 0 || event.button == 2) {
|
||||
var target = event.originalTarget;
|
||||
|
||||
while (target && !("_IsConsoleRow" in target))
|
||||
target = target.parentNode;
|
||||
|
||||
if (target)
|
||||
this.selectedItem = target;
|
||||
}
|
||||
]]></handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="error" extends="xul:box">
|
||||
<content>
|
||||
<xul:box class="console-row-internal-box" flex="1">
|
||||
<xul:box class="console-row-icon" align="center" xbl:inherits="selected">
|
||||
<xul:image class="console-icon" xbl:inherits="src,type"/>
|
||||
</xul:box>
|
||||
<xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
|
||||
<xul:box class="console-row-msg" align="start">
|
||||
<xul:label class="label" xbl:inherits="value=typetext"/>
|
||||
<xul:description class="console-error-msg" xbl:inherits="xbl:text=msg" flex="1"/>
|
||||
<xul:label class="label console-time" xbl:inherits="value=time"/>
|
||||
</xul:box>
|
||||
<xul:box class="console-row-file" xbl:inherits="hidden=hideSource">
|
||||
<xul:label class="label" value="&errFile.label;"/>
|
||||
<xul:box class="console-error-source" xbl:inherits="href,line"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:hbox class="lineNumberRow" xbl:inherits="line">
|
||||
<xul:label class="label" value="&errLine.label;"/>
|
||||
<xul:label class="label" xbl:inherits="value=line"/>
|
||||
</xul:hbox>
|
||||
</xul:box>
|
||||
<xul:vbox class="console-row-code" xbl:inherits="selected,hidden=hideCode">
|
||||
<xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
|
||||
<xul:box xbl:inherits="hidden=hideCaret">
|
||||
<xul:label class="monospace console-dots" xbl:inherits="value=errorDots"/>
|
||||
<xul:label class="monospace console-caret" xbl:inherits="value=errorCaret"/>
|
||||
<xul:spacer flex="1"/>
|
||||
</xul:box>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
</xul:box>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<field name="mSourceName">null</field>
|
||||
<field name="mSourceLine">null</field>
|
||||
|
||||
<method name="toString">
|
||||
<body><![CDATA[
|
||||
let msg = "";
|
||||
let strBundle = this._ConsoleBox.mStrBundle;
|
||||
|
||||
if (this.hasAttribute("time"))
|
||||
msg += strBundle.getFormattedString("errTime", [this.getAttribute("time")]) + "\n";
|
||||
|
||||
msg += this.getAttribute("typetext") + " " + this.getAttribute("msg");
|
||||
|
||||
if (this.hasAttribute("line") && this.mSourceName) {
|
||||
msg += "\n" + strBundle.getFormattedString("errFile",
|
||||
[this.mSourceName]) + "\n";
|
||||
if (this.hasAttribute("col")) {
|
||||
msg += strBundle.getFormattedString("errLineCol",
|
||||
[this.getAttribute("line"), this.getAttribute("col")]);
|
||||
} else
|
||||
msg += strBundle.getFormattedString("errLine", [this.getAttribute("line")]);
|
||||
}
|
||||
|
||||
if (this.hasAttribute("code"))
|
||||
msg += "\n" + strBundle.getString("errCode") + "\n" + this.mSourceLine;
|
||||
|
||||
return msg;
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
</binding>
|
||||
|
||||
<binding id="message" extends="xul:box">
|
||||
<content>
|
||||
<xul:box class="console-internal-box" flex="1">
|
||||
<xul:box class="console-row-icon" align="center">
|
||||
<xul:image class="console-icon" xbl:inherits="src,type"/>
|
||||
</xul:box>
|
||||
<xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
|
||||
<xul:vbox class="console-row-msg" flex="1">
|
||||
<xul:description class="console-msg-text" xbl:inherits="xbl:text=msg"/>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
</xul:box>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<method name="toString">
|
||||
<body><![CDATA[
|
||||
return this.getAttribute("msg");
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="console-error-source" extends="xul:box">
|
||||
<content>
|
||||
<xul:label class="text-link" xbl:inherits="value=href" crop="right"/>
|
||||
</content>
|
||||
|
||||
<handlers>
|
||||
<handler event="click" phase="capturing" button="0" preventdefault="true">
|
||||
<![CDATA[
|
||||
var url = document.getBindingParent(this).mSourceName;
|
||||
url = url.substring(url.lastIndexOf(" ") + 1);
|
||||
var line = getAttribute("line");
|
||||
gViewSourceUtils.viewSource({URL: url, lineNumber: line});
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
@ -1,9 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
toolkit.jar:
|
||||
content/global/console.js (content/console.js)
|
||||
content/global/console.xul (content/console.xul)
|
||||
content/global/console.css (content/console.css)
|
||||
content/global/consoleBindings.xml (content/consoleBindings.xml)
|
@ -1,40 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
|
||||
/* vim:sw=4:sr:sta:et:sts: */
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function jsConsoleHandler() {}
|
||||
jsConsoleHandler.prototype = {
|
||||
handle: function clh_handle(cmdLine) {
|
||||
if (!cmdLine.handleFlag("jsconsole", false))
|
||||
return;
|
||||
|
||||
var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator);
|
||||
var console = wm.getMostRecentWindow("global:console");
|
||||
if (!console) {
|
||||
var wwatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Ci.nsIWindowWatcher);
|
||||
wwatch.openWindow(null, "chrome://global/content/console.xul", "_blank",
|
||||
"chrome,dialog=no,all", cmdLine);
|
||||
} else {
|
||||
console.focus(); // the Error console was already open
|
||||
}
|
||||
|
||||
if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO)
|
||||
cmdLine.preventDefault = true;
|
||||
},
|
||||
|
||||
helpInfo : " --jsconsole Open the Error console.\n",
|
||||
|
||||
classID: Components.ID("{2cd0c310-e127-44d0-88fc-4435c9ab4d4b}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([jsConsoleHandler]);
|
@ -1,3 +0,0 @@
|
||||
component {2cd0c310-e127-44d0-88fc-4435c9ab4d4b} jsconsole-clhandler.js
|
||||
contract @mozilla.org/toolkit/console-clh;1 {2cd0c310-e127-44d0-88fc-4435c9ab4d4b}
|
||||
category command-line-handler t-jsconsole @mozilla.org/toolkit/console-clh;1
|
@ -1,17 +0,0 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'jsconsole-clhandler.js',
|
||||
'jsconsole-clhandler.manifest',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Error Console')
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../../../testing/mochitest/chrome.eslintrc"
|
||||
]
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
[DEFAULT]
|
||||
skip-if = buildapp == 'b2g'
|
||||
|
||||
[test_hugeURIs.xul]
|
@ -1,64 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=796179
|
||||
-->
|
||||
<window title="Mozilla Bug 796179"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="RunTest();">
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
|
||||
<!-- Detect severe performance and memory issues when large amounts of errors
|
||||
are reported from CSS embedded in a file with a long data URI. Addressed
|
||||
by 786108 for issues internal to the style system and by 796179 for issues
|
||||
related to the error console. This error console test should finish quickly
|
||||
with those patches and run for a very long time or OOM otherwise. -->
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=796179"
|
||||
target="_blank">Mozilla Bug 796179</a>
|
||||
<div id="badSVG" style="max-width: 1; max-height: 1; overflow: hidden"></div>
|
||||
</body>
|
||||
|
||||
<!-- display the error console so we can test its reaction to the test -->
|
||||
<iframe id="errorConsoleFrame" height="400" src="chrome://global/content/console.xul"></iframe>
|
||||
|
||||
<!-- test code -->
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
function RunTest()
|
||||
{
|
||||
// Create the bad SVG and add it to the document.
|
||||
var img = new Array;
|
||||
img.push('<img src="data:image/svg+xml,');
|
||||
img.push(encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="300px">'));
|
||||
|
||||
for (var i = 0 ; i < 10000 ; i++)
|
||||
img.push(encodeURIComponent('<circle cx="0" cy="0" r="1" style="xxx-invalid-property: 0;"/>'));
|
||||
|
||||
img.push(encodeURIComponent('</svg>'));
|
||||
img.push('" />');
|
||||
|
||||
document.getElementById('badSVG').innerHTML = img.join('');
|
||||
|
||||
// We yield control of the thread, allowing the error console to render.
|
||||
// If we get control back without timing out or OOMing then the test passed.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.executeSoon(function() {
|
||||
// Clean up.
|
||||
var elem = document.getElementById('errorConsoleFrame');
|
||||
elem.parentNode.removeChild(elem);
|
||||
elem = document.getElementById('badSVG');
|
||||
elem.parentNode.removeChild(elem);
|
||||
elem = null;
|
||||
|
||||
// Finish the test with a pass.
|
||||
ok(true, 'Error console rendered OK.');
|
||||
SimpleTest.finish();
|
||||
}, 0);
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
@ -18,7 +18,6 @@ DIRS += [
|
||||
'apppicker',
|
||||
'asyncshutdown',
|
||||
'commandlines',
|
||||
'console',
|
||||
'contentprefs',
|
||||
'cookie',
|
||||
'crashmonitor',
|
||||
|
@ -204,6 +204,42 @@ this.LoginHelper = {
|
||||
return false;
|
||||
},
|
||||
|
||||
doLoginsMatch(aLogin1, aLogin2, {
|
||||
ignorePassword = false,
|
||||
ignoreSchemes = false,
|
||||
}) {
|
||||
if (aLogin1.httpRealm != aLogin2.httpRealm ||
|
||||
aLogin1.username != aLogin2.username)
|
||||
return false;
|
||||
|
||||
if (!ignorePassword && aLogin1.password != aLogin2.password)
|
||||
return false;
|
||||
|
||||
if (ignoreSchemes) {
|
||||
let hostname1URI = Services.io.newURI(aLogin1.hostname, null, null);
|
||||
let hostname2URI = Services.io.newURI(aLogin2.hostname, null, null);
|
||||
if (hostname1URI.hostPort != hostname2URI.hostPort)
|
||||
return false;
|
||||
|
||||
if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
|
||||
Services.io.newURI(aLogin1.formSubmitURL, null, null).hostPort !=
|
||||
Services.io.newURI(aLogin2.formSubmitURL, null, null).hostPort)
|
||||
return false;
|
||||
} else {
|
||||
if (aLogin1.hostname != aLogin2.hostname)
|
||||
return false;
|
||||
|
||||
// If either formSubmitURL is blank (but not null), then match.
|
||||
if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
|
||||
aLogin1.formSubmitURL != aLogin2.formSubmitURL)
|
||||
return false;
|
||||
}
|
||||
|
||||
// The .usernameField and .passwordField values are ignored.
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new login object that results by modifying the given object with
|
||||
* the provided data.
|
||||
|
@ -334,6 +334,15 @@ var LoginManagerParent = {
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
|
||||
// Dedupe so the length checks below still make sense with scheme upgrades.
|
||||
// Below here we have one login per hostPort + action + username with the
|
||||
// matching scheme being preferred.
|
||||
let resolveBy = [
|
||||
"scheme",
|
||||
"timePasswordChanged",
|
||||
];
|
||||
logins = LoginHelper.dedupeLogins(logins, ["username"], resolveBy, hostname);
|
||||
|
||||
// If we didn't find a username field, but seem to be changing a
|
||||
// password, allow the user to select from a list of applicable
|
||||
// logins to update the password for.
|
||||
@ -355,6 +364,10 @@ var LoginManagerParent = {
|
||||
|
||||
prompter.promptToChangePassword(oldLogin, formLogin);
|
||||
} else {
|
||||
// Note: It's possible that that we already have the correct u+p saved
|
||||
// but since we don't have the username, we don't know if the user is
|
||||
// changing a second account to the new password so we ask anyways.
|
||||
|
||||
prompter.promptToChangePasswordWithUsernames(
|
||||
logins, logins.length, formLogin);
|
||||
}
|
||||
@ -365,8 +378,8 @@ var LoginManagerParent = {
|
||||
|
||||
var existingLogin = null;
|
||||
// Look for an existing login that matches the form login.
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
var same, login = logins[i];
|
||||
for (let login of logins) {
|
||||
let same;
|
||||
|
||||
// If one login has a username but the other doesn't, ignore
|
||||
// the username when comparing and only match if they have the
|
||||
@ -375,14 +388,23 @@ var LoginManagerParent = {
|
||||
if (!login.username && formLogin.username) {
|
||||
var restoreMe = formLogin.username;
|
||||
formLogin.username = "";
|
||||
same = formLogin.matches(login, false);
|
||||
same = LoginHelper.doLoginsMatch(formLogin, login, {
|
||||
ignorePassword: false,
|
||||
ignoreSchemes: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
formLogin.username = restoreMe;
|
||||
} else if (!formLogin.username && login.username) {
|
||||
formLogin.username = login.username;
|
||||
same = formLogin.matches(login, false);
|
||||
same = LoginHelper.doLoginsMatch(formLogin, login, {
|
||||
ignorePassword: false,
|
||||
ignoreSchemes: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
formLogin.username = ""; // we know it's always blank.
|
||||
} else {
|
||||
same = formLogin.matches(login, true);
|
||||
same = LoginHelper.doLoginsMatch(formLogin, login, {
|
||||
ignorePassword: true,
|
||||
ignoreSchemes: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
}
|
||||
|
||||
if (same) {
|
||||
|
@ -6,6 +6,10 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
|
||||
"resource://gre/modules/LoginHelper.jsm");
|
||||
|
||||
|
||||
function nsLoginInfo() {}
|
||||
|
||||
nsLoginInfo.prototype = {
|
||||
@ -38,22 +42,9 @@ nsLoginInfo.prototype = {
|
||||
},
|
||||
|
||||
matches(aLogin, ignorePassword) {
|
||||
if (this.hostname != aLogin.hostname ||
|
||||
this.httpRealm != aLogin.httpRealm ||
|
||||
this.username != aLogin.username)
|
||||
return false;
|
||||
|
||||
if (!ignorePassword && this.password != aLogin.password)
|
||||
return false;
|
||||
|
||||
// If either formSubmitURL is blank (but not null), then match.
|
||||
if (this.formSubmitURL != "" && aLogin.formSubmitURL != "" &&
|
||||
this.formSubmitURL != aLogin.formSubmitURL)
|
||||
return false;
|
||||
|
||||
// The .usernameField and .passwordField values are ignored.
|
||||
|
||||
return true;
|
||||
return LoginHelper.doLoginsMatch(this, aLogin, {
|
||||
ignorePassword,
|
||||
});
|
||||
},
|
||||
|
||||
equals : function (aLogin) {
|
||||
|
@ -27,9 +27,7 @@ const PROMPT_ADD_OR_UPDATE = 1;
|
||||
const PROMPT_NOTNOW = 2;
|
||||
const PROMPT_NEVER = 3;
|
||||
|
||||
/*
|
||||
* LoginManagerPromptFactory
|
||||
*
|
||||
/**
|
||||
* Implements nsIPromptFactory
|
||||
*
|
||||
* Invoked by [toolkit/components/prompts/src/nsPrompter.js]
|
||||
@ -186,9 +184,7 @@ XPCOMUtils.defineLazyGetter(this.LoginManagerPromptFactory.prototype, "log", ()
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* LoginManagerPrompter
|
||||
*
|
||||
/**
|
||||
* Implements interfaces for prompting the user to enter/save/change auth info.
|
||||
*
|
||||
* nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
|
||||
@ -279,9 +275,7 @@ LoginManagerPrompter.prototype = {
|
||||
/* ---------- nsIAuthPrompt prompts ---------- */
|
||||
|
||||
|
||||
/*
|
||||
* prompt
|
||||
*
|
||||
/**
|
||||
* Wrapper around the prompt service prompt. Saving random fields here
|
||||
* doesn't really make sense and therefore isn't implemented.
|
||||
*/
|
||||
@ -302,9 +296,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* promptUsernameAndPassword
|
||||
*
|
||||
/**
|
||||
* Looks up a username and password in the database. Will prompt the user
|
||||
* with a dialog, even if a username and password are found.
|
||||
*/
|
||||
@ -401,9 +393,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* promptPassword
|
||||
*
|
||||
/**
|
||||
* If a password is found in the database for the password realm, it is
|
||||
* returned straight away without displaying a dialog.
|
||||
*
|
||||
@ -508,14 +498,12 @@ LoginManagerPrompter.prototype = {
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* promptAuth
|
||||
*
|
||||
/**
|
||||
* Implementation of nsIAuthPrompt2.
|
||||
*
|
||||
* nsIChannel aChannel
|
||||
* int aLevel
|
||||
* nsIAuthInformation aAuthInfo
|
||||
* @param {nsIChannel} aChannel
|
||||
* @param {int} aLevel
|
||||
* @param {nsIAuthInformation} aAuthInfo
|
||||
*/
|
||||
promptAuth : function (aChannel, aLevel, aAuthInfo) {
|
||||
var selectedLogin = null;
|
||||
@ -694,11 +682,6 @@ LoginManagerPrompter.prototype = {
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* init
|
||||
*
|
||||
*/
|
||||
init : function (aWindow, aFactory) {
|
||||
this._window = aWindow;
|
||||
this._factory = aFactory || null;
|
||||
@ -715,11 +698,6 @@ LoginManagerPrompter.prototype = {
|
||||
this._opener = aOpener;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* promptToSavePassword
|
||||
*
|
||||
*/
|
||||
promptToSavePassword : function (aLogin) {
|
||||
this.log("promptToSavePassword");
|
||||
var notifyObj = this._getPopupNote() || this._getNotifyBox();
|
||||
@ -729,12 +707,8 @@ LoginManagerPrompter.prototype = {
|
||||
this._showSaveLoginDialog(aLogin);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _showLoginNotification
|
||||
*
|
||||
/**
|
||||
* Displays a notification bar.
|
||||
*
|
||||
*/
|
||||
_showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
|
||||
var oldBar = aNotifyBox.getNotificationWithValue(aName);
|
||||
@ -820,9 +794,13 @@ LoginManagerPrompter.prototype = {
|
||||
};
|
||||
|
||||
let updateButtonLabel = () => {
|
||||
let foundLogins = Services.logins.findLogins({}, login.hostname,
|
||||
login.formSubmitURL,
|
||||
login.httpRealm);
|
||||
let foundLogins = LoginHelper.searchLoginsWithObject({
|
||||
formSubmitURL: login.formSubmitURL,
|
||||
hostname: login.hostname,
|
||||
httpRealm: login.httpRealm,
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
|
||||
let logins = this._filterUpdatableLogins(login, foundLogins);
|
||||
let msgNames = (logins.length == 0) ? saveMsgNames : changeMsgNames;
|
||||
|
||||
@ -886,9 +864,13 @@ LoginManagerPrompter.prototype = {
|
||||
};
|
||||
|
||||
let persistData = () => {
|
||||
let foundLogins = Services.logins.findLogins({}, login.hostname,
|
||||
login.formSubmitURL,
|
||||
login.httpRealm);
|
||||
let foundLogins = LoginHelper.searchLoginsWithObject({
|
||||
formSubmitURL: login.formSubmitURL,
|
||||
hostname: login.hostname,
|
||||
httpRealm: login.httpRealm,
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
});
|
||||
|
||||
let logins = this._filterUpdatableLogins(login, foundLogins);
|
||||
|
||||
if (logins.length == 0) {
|
||||
@ -1002,15 +984,15 @@ LoginManagerPrompter.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
/*
|
||||
* _showSaveLoginNotification
|
||||
*
|
||||
/**
|
||||
* Displays a notification bar or a popup notification, to allow the user
|
||||
* to save the specified login. This allows the user to see the results of
|
||||
* their login, and only save a login which they know worked.
|
||||
*
|
||||
* @param aNotifyObj
|
||||
* A notification box or a popup notification.
|
||||
* @param aLogin
|
||||
* The login captured from the form.
|
||||
*/
|
||||
_showSaveLoginNotification : function (aNotifyObj, aLogin) {
|
||||
// Ugh. We can't use the strings from the popup window, because they
|
||||
@ -1079,11 +1061,6 @@ LoginManagerPrompter.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _removeLoginNotifications
|
||||
*
|
||||
*/
|
||||
_removeLoginNotifications : function () {
|
||||
var popupNote = this._getPopupNote();
|
||||
if (popupNote)
|
||||
@ -1108,12 +1085,9 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _showSaveLoginDialog
|
||||
*
|
||||
/**
|
||||
* Called when we detect a new login in a form submission,
|
||||
* asks the user what to do.
|
||||
*
|
||||
*/
|
||||
_showSaveLoginDialog : function (aLogin) {
|
||||
const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
|
||||
@ -1189,9 +1163,7 @@ LoginManagerPrompter.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* _showChangeLoginNotification
|
||||
*
|
||||
/**
|
||||
* Shows the Change Password notification bar or popup notification.
|
||||
*
|
||||
* @param aNotifyObj
|
||||
@ -1202,7 +1174,6 @@ LoginManagerPrompter.prototype = {
|
||||
*
|
||||
* @param aNewLogin
|
||||
* The login object with the changes we want to make.
|
||||
*
|
||||
*/
|
||||
_showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin) {
|
||||
var changeButtonText =
|
||||
@ -1223,6 +1194,8 @@ LoginManagerPrompter.prototype = {
|
||||
|
||||
// Notification is a PopupNotification
|
||||
if (aNotifyObj == this._getPopupNote()) {
|
||||
aOldLogin.hostname = aNewLogin.hostname;
|
||||
aOldLogin.formSubmitURL = aNewLogin.formSubmitURL;
|
||||
aOldLogin.password = aNewLogin.password;
|
||||
aOldLogin.username = aNewLogin.username;
|
||||
this._showLoginCaptureDoorhanger(aOldLogin, "password-change");
|
||||
@ -1259,11 +1232,8 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _showChangeLoginDialog
|
||||
*
|
||||
/**
|
||||
* Shows the Change Password dialog.
|
||||
*
|
||||
*/
|
||||
_showChangeLoginDialog(aOldLogin, aNewLogin) {
|
||||
const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
|
||||
@ -1292,9 +1262,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* promptToChangePasswordWithUsernames
|
||||
*
|
||||
/**
|
||||
* Called when we detect a password change in a form submission, but we
|
||||
* don't know which existing login (username) it's for. Asks the user
|
||||
* to select a username and confirm the password change.
|
||||
@ -1347,6 +1315,8 @@ LoginManagerPrompter.prototype = {
|
||||
var propBag = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag);
|
||||
if (aNewLogin) {
|
||||
propBag.setProperty("formSubmitURL", aNewLogin.formSubmitURL);
|
||||
propBag.setProperty("hostname", aNewLogin.hostname);
|
||||
propBag.setProperty("password", aNewLogin.password);
|
||||
propBag.setProperty("username", aNewLogin.username);
|
||||
// Explicitly set the password change time here (even though it would
|
||||
@ -1360,9 +1330,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getChromeWindow
|
||||
*
|
||||
/**
|
||||
* Given a content DOM window, returns the chrome window it's in.
|
||||
*/
|
||||
_getChromeWindow: function (aWindow) {
|
||||
@ -1377,9 +1345,6 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getNotifyWindow
|
||||
*/
|
||||
_getNotifyWindow: function () {
|
||||
|
||||
try {
|
||||
@ -1445,9 +1410,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getPopupNote
|
||||
*
|
||||
/**
|
||||
* Returns the popup notification to this prompter,
|
||||
* or null if there isn't one available.
|
||||
*/
|
||||
@ -1470,9 +1433,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getNotifyBox
|
||||
*
|
||||
/**
|
||||
* Returns the notification box to this prompter, or null if there isn't
|
||||
* a notification box available.
|
||||
*/
|
||||
@ -1495,9 +1456,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _repickSelectedLogin
|
||||
*
|
||||
/**
|
||||
* The user might enter a login that isn't the one we prefilled, but
|
||||
* is the same as some other existing login. So, pick a login with a
|
||||
* matching username, or return null.
|
||||
@ -1510,9 +1469,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getLocalizedString
|
||||
*
|
||||
/**
|
||||
* Can be called as:
|
||||
* _getLocalizedString("key1");
|
||||
* _getLocalizedString("key2", ["arg1"]);
|
||||
@ -1532,9 +1489,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _sanitizeUsername
|
||||
*
|
||||
/**
|
||||
* Sanitizes the specified username, by stripping quotes and truncating if
|
||||
* it's too long. This helps prevent an evil site from messing with the
|
||||
* "save password?" prompt too much.
|
||||
@ -1566,9 +1521,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getShortDisplayHost
|
||||
*
|
||||
/**
|
||||
* Converts a login's hostname field (a URL) to a short string for
|
||||
* prompting purposes. Eg, "http://foo.com" --> "foo.com", or
|
||||
* "ftp://www.site.co.uk" --> "site.co.uk".
|
||||
@ -1595,9 +1548,7 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getAuthTarget
|
||||
*
|
||||
/**
|
||||
* Returns the hostname and realm for which authentication is being
|
||||
* requested, in the format expected to be used with nsILoginInfo.
|
||||
*/
|
||||
|
@ -359,7 +359,8 @@ this.LoginManagerStorage_json.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
this.log("_searchLogins: returning", foundLogins.length, "logins for", matchData);
|
||||
this.log("_searchLogins: returning", foundLogins.length, "logins for", matchData,
|
||||
"with options", aOptions);
|
||||
return [foundLogins, foundIds];
|
||||
},
|
||||
|
||||
|
@ -7,13 +7,15 @@ const BRAND_SHORT_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
|
||||
|
||||
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
Ci.nsILoginInfo, "init");
|
||||
let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login1 = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"notifyu1", "notifyp1", "user", "pass");
|
||||
let login2 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login1HTTPS = new nsLoginInfo("https://example.com", "https://example.com", null,
|
||||
"notifyu1", "notifyp1", "user", "pass");
|
||||
let login2 = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"", "notifyp1", "", "pass");
|
||||
let login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login1B = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"notifyu1B", "notifyp1B", "user", "pass");
|
||||
let login2B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login2B = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"", "notifyp1B", "", "pass");
|
||||
|
||||
requestLongerTimeout(2);
|
||||
@ -46,7 +48,7 @@ add_task(function* test_clickNever() {
|
||||
is(fieldValues.password, "notifyp1", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-save");
|
||||
ok(notif, "got notification popup");
|
||||
is(true, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
|
||||
is(true, Services.logins.getLoginSavingEnabled("http://example.com"),
|
||||
"Checking for login saving enabled");
|
||||
clickDoorhangerButton(notif, NEVER_BUTTON);
|
||||
});
|
||||
@ -59,9 +61,9 @@ add_task(function* test_clickNever() {
|
||||
is(fieldValues.password, "notifyp1", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-save");
|
||||
ok(!notif, "checking for no notification popup");
|
||||
is(false, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
|
||||
is(false, Services.logins.getLoginSavingEnabled("http://example.com"),
|
||||
"Checking for login saving disabled");
|
||||
Services.logins.setLoginSavingEnabled("http://mochi.test:8888", true);
|
||||
Services.logins.setLoginSavingEnabled("http://example.com", true);
|
||||
});
|
||||
|
||||
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
|
||||
@ -80,7 +82,7 @@ add_task(function* test_clickRemember() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username used on the new entry");
|
||||
is(login.password, "notifyp1", "Check the password used on the new entry");
|
||||
is(login.timesUsed, 1, "Check times used on new entry");
|
||||
@ -95,7 +97,7 @@ add_task(function* test_clickRemember() {
|
||||
|
||||
logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username used");
|
||||
is(login.password, "notifyp1", "Check the password used");
|
||||
is(login.timesUsed, 2, "Check times used incremented");
|
||||
@ -209,7 +211,7 @@ add_task(function* test_pwOnlyLoginMatchesForm() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "", "Check the username");
|
||||
is(login.password, "notifyp1", "Check the password");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -230,7 +232,7 @@ add_task(function* test_pwOnlyFormMatchesLogin() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username");
|
||||
is(login.password, "notifyp1", "Check the password");
|
||||
is(login.timesUsed, 2, "Check times used");
|
||||
@ -252,7 +254,7 @@ add_task(function* test_pwOnlyFormDoesntMatchExisting() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1B", "Check the username unchanged");
|
||||
is(login.password, "notifyp1B", "Check the password unchanged");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -274,7 +276,7 @@ add_task(function* test_changeUPLoginOnUPForm_dont() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username unchanged");
|
||||
is(login.password, "notifyp1", "Check the password unchanged");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -297,7 +299,7 @@ add_task(function* test_changeUPLoginOnUPForm_change() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username unchanged");
|
||||
is(login.password, "pass2", "Check the password changed");
|
||||
is(login.timesUsed, 2, "Check times used");
|
||||
@ -325,7 +327,7 @@ add_task(function* test_changePLoginOnUPForm() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "", "Check the username unchanged");
|
||||
is(login.password, "pass2", "Check the password changed");
|
||||
is(login.timesUsed, 2, "Check times used");
|
||||
@ -347,7 +349,7 @@ add_task(function* test_changePLoginOnPForm() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "", "Check the username unchanged");
|
||||
is(login.password, "notifyp1", "Check the password changed");
|
||||
is(login.timesUsed, 3, "Check times used");
|
||||
@ -422,7 +424,7 @@ add_task(function* test_change2pw0unExistingDifferentUP() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1B", "Check the username unchanged");
|
||||
is(login.password, "notifyp1B", "Check the password unchanged");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -446,7 +448,7 @@ add_task(function* test_change2pw0unExistingDifferentP() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "", "Check the username unchanged");
|
||||
is(login.password, "notifyp1B", "Check the password unchanged");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -469,7 +471,7 @@ add_task(function* test_change2pw0unExistingWithSameP() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "", "Check the username unchanged");
|
||||
is(login.password, "notifyp1", "Check the password unchanged");
|
||||
is(login.timesUsed, 2, "Check times used incremented");
|
||||
@ -494,7 +496,7 @@ add_task(function* test_changeUPLoginOnPUpdateForm() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username unchanged");
|
||||
is(login.password, "pass2", "Check the password changed");
|
||||
is(login.timesUsed, 2, "Check times used");
|
||||
@ -525,7 +527,7 @@ add_task(function* test_recipeCaptureFields_NewLogin() {
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username unchanged");
|
||||
is(login.password, "notifyp1", "Check the password unchanged");
|
||||
is(login.timesUsed, 1, "Check times used");
|
||||
@ -545,7 +547,7 @@ add_task(function* test_recipeCaptureFields_ExistingLogin() {
|
||||
checkOnlyLoginWasUsedTwice({ justChanged: false });
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login");
|
||||
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username unchanged");
|
||||
is(login.password, "notifyp1", "Check the password unchanged");
|
||||
is(login.timesUsed, 2, "Check times used incremented");
|
||||
@ -576,5 +578,114 @@ add_task(function* test_noShowPasswordOnDismissal() {
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* test_httpsUpgradeCaptureFields_noChange() {
|
||||
info("Check that we don't prompt to remember when capturing an upgraded login with no change");
|
||||
Services.logins.addLogin(login1);
|
||||
// Sanity check the HTTP login exists.
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should have the HTTP login");
|
||||
|
||||
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
|
||||
is(fieldValues.username, "notifyu1", "Checking submitted username");
|
||||
is(fieldValues.password, "notifyp1", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-save");
|
||||
ok(!notif, "checking for no notification popup");
|
||||
}, "https://example.com"); // This is HTTPS whereas the saved login is HTTP
|
||||
|
||||
logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login still");
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.hostname, "http://example.com", "Check the hostname is unchanged");
|
||||
is(login.username, "notifyu1", "Check the username is unchanged");
|
||||
is(login.password, "notifyp1", "Check the password is unchanged");
|
||||
is(login.timesUsed, 2, "Check times used increased");
|
||||
|
||||
Services.logins.removeLogin(login1);
|
||||
});
|
||||
|
||||
add_task(function* test_httpsUpgradeCaptureFields_changePW() {
|
||||
info("Check that we prompt to change when capturing an upgraded login with a new PW");
|
||||
Services.logins.addLogin(login1);
|
||||
// Sanity check the HTTP login exists.
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should have the HTTP login");
|
||||
|
||||
yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
|
||||
is(fieldValues.username, "notifyu1", "Checking submitted username");
|
||||
is(fieldValues.password, "pass2", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-change");
|
||||
ok(notif, "checking for a change popup");
|
||||
clickDoorhangerButton(notif, CHANGE_BUTTON);
|
||||
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
|
||||
}, "https://example.com"); // This is HTTPS whereas the saved login is HTTP
|
||||
|
||||
checkOnlyLoginWasUsedTwice({ justChanged: true });
|
||||
logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 1, "Should only have 1 login still");
|
||||
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.hostname, "https://example.com", "Check the hostname is upgraded");
|
||||
is(login.formSubmitURL, "https://example.com", "Check the formSubmitURL is upgraded");
|
||||
is(login.username, "notifyu1", "Check the username is unchanged");
|
||||
is(login.password, "pass2", "Check the password changed");
|
||||
is(login.timesUsed, 2, "Check times used increased");
|
||||
|
||||
Services.logins.removeAllLogins();
|
||||
});
|
||||
|
||||
add_task(function* test_httpsUpgradeCaptureFields_captureMatchingHTTP() {
|
||||
info("Capture a new HTTP login which matches a stored HTTPS one.");
|
||||
Services.logins.addLogin(login1HTTPS);
|
||||
|
||||
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
|
||||
is(fieldValues.username, "notifyu1", "Checking submitted username");
|
||||
is(fieldValues.password, "notifyp1", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-save");
|
||||
ok(notif, "got notification popup");
|
||||
|
||||
is(Services.logins.getAllLogins().length, 1, "Should only have the HTTPS login");
|
||||
clickDoorhangerButton(notif, REMEMBER_BUTTON);
|
||||
});
|
||||
|
||||
let logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 2, "Should have both HTTP and HTTPS logins");
|
||||
for (let login of logins) {
|
||||
login = login.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
is(login.username, "notifyu1", "Check the username used on the new entry");
|
||||
is(login.password, "notifyp1", "Check the password used on the new entry");
|
||||
is(login.timesUsed, 1, "Check times used on entry");
|
||||
}
|
||||
|
||||
info("Make sure Remember took effect and we don't prompt for an existing HTTP login");
|
||||
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
|
||||
is(fieldValues.username, "notifyu1", "Checking submitted username");
|
||||
is(fieldValues.password, "notifyp1", "Checking submitted password");
|
||||
let notif = getCaptureDoorhanger("password-save");
|
||||
ok(!notif, "checking for no notification popup");
|
||||
});
|
||||
|
||||
logins = Services.logins.getAllLogins();
|
||||
is(logins.length, 2, "Should have both HTTP and HTTPS still");
|
||||
|
||||
let httpsLogins = LoginHelper.searchLoginsWithObject({
|
||||
hostname: "https://example.com",
|
||||
});
|
||||
is(httpsLogins.length, 1, "Check https logins count");
|
||||
let httpsLogin = httpsLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
ok(httpsLogin.equals(login1HTTPS), "Check HTTPS login didn't change");
|
||||
is(httpsLogin.timesUsed, 1, "Check times used");
|
||||
|
||||
let httpLogins = LoginHelper.searchLoginsWithObject({
|
||||
hostname: "http://example.com",
|
||||
});
|
||||
is(httpLogins.length, 1, "Check http logins count");
|
||||
let httpLogin = httpLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
ok(httpLogin.equals(login1), "Check HTTP login is as expected");
|
||||
is(httpLogin.timesUsed, 2, "Check times used increased");
|
||||
|
||||
Services.logins.removeLogin(login1);
|
||||
Services.logins.removeLogin(login1HTTPS);
|
||||
});
|
||||
|
||||
|
||||
// TODO:
|
||||
// * existing login test, form has different password --> change password, no save prompt
|
||||
|
@ -39,9 +39,9 @@ function getSelectDialogDoc() {
|
||||
|
||||
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
Ci.nsILoginInfo, "init");
|
||||
let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login1 = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"notifyu1", "notifyp1", "user", "pass");
|
||||
let login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
|
||||
let login1B = new nsLoginInfo("http://example.com", "http://example.com", null,
|
||||
"notifyu1B", "notifyp1B", "user", "pass");
|
||||
|
||||
add_task(function* test_changeUPLoginOnPUpdateForm_accept() {
|
||||
|
@ -20,10 +20,10 @@ registerCleanupFunction(function* cleanup_removeAllLoginsAndResetRecipes() {
|
||||
*
|
||||
* @param {String} aPageFile - test page file name which auto-submits to formsubmit.sjs
|
||||
* @param {Function} aTaskFn - task which can be run before the tab closes.
|
||||
* @param {String} [aOrigin="http://mochi.test:8888"] - origin of the server to
|
||||
* use to load `aPageFile`.
|
||||
* @param {String} [aOrigin="http://example.com"] - origin of the server to use
|
||||
* to load `aPageFile`.
|
||||
*/
|
||||
function testSubmittingLoginForm(aPageFile, aTaskFn, aOrigin = "http://mochi.test:8888") {
|
||||
function testSubmittingLoginForm(aPageFile, aTaskFn, aOrigin = "http://example.com") {
|
||||
return BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: aOrigin + DIRECTORY_PATH + aPageFile,
|
||||
|
@ -1222,14 +1222,17 @@
|
||||
event.preventDefault(); // Prevent page scrolling
|
||||
},
|
||||
|
||||
get videoSubtitles() {
|
||||
return Array.prototype.filter.call(this.video.textTracks, function (tt) {
|
||||
return tt.kind === "subtitles";
|
||||
});
|
||||
isSupportedTextTrack : function(textTrack) {
|
||||
return textTrack.kind == "subtitles" ||
|
||||
textTrack.kind == "captions";
|
||||
},
|
||||
|
||||
get overlayableTextTracks() {
|
||||
return Array.prototype.filter.call(this.video.textTracks, this.isSupportedTextTrack);
|
||||
},
|
||||
|
||||
isClosedCaptionOn : function () {
|
||||
for (let tt of this.videoSubtitles) {
|
||||
for (let tt of this.overlayableTextTracks) {
|
||||
if (tt.mode === "showing") {
|
||||
return true;
|
||||
}
|
||||
@ -1239,7 +1242,7 @@
|
||||
},
|
||||
|
||||
setClosedCaptionButtonState : function () {
|
||||
if (!this.videoSubtitles.length || this.videocontrols.isTouchControl) {
|
||||
if (!this.overlayableTextTracks.length || this.videocontrols.isTouchControl) {
|
||||
this.closedCaptionButton.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
@ -1266,7 +1269,7 @@
|
||||
},
|
||||
|
||||
addNewTextTrack : function (tt) {
|
||||
if (tt.kind !== "subtitles") {
|
||||
if (!this.isSupportedTextTrack(tt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1299,7 +1302,7 @@
|
||||
},
|
||||
|
||||
changeTextTrack : function (index) {
|
||||
for (let tt of this.videoSubtitles) {
|
||||
for (let tt of this.overlayableTextTracks) {
|
||||
if (tt.index === index) {
|
||||
tt.mode = "showing";
|
||||
|
||||
@ -1323,8 +1326,8 @@
|
||||
},
|
||||
|
||||
toggleClosedCaption : function () {
|
||||
if (this.videoSubtitles.length === 1) {
|
||||
const lastTTIdx = this.videoSubtitles[0].index;
|
||||
if (this.overlayableTextTracks.length === 1) {
|
||||
const lastTTIdx = this.overlayableTextTracks[0].index;
|
||||
|
||||
return this.changeTextTrack(this.isClosedCaptionOn() ? 0 : lastTTIdx);
|
||||
}
|
||||
@ -1391,7 +1394,7 @@
|
||||
kind: "subtitles"
|
||||
});
|
||||
|
||||
for (let tt of this.videoSubtitles) {
|
||||
for (let tt of this.overlayableTextTracks) {
|
||||
this.addNewTextTrack(tt);
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,6 @@
|
||||
locale/@AB_CD@/global/commonDialogs.properties (%chrome/global/commonDialogs.properties)
|
||||
locale/@AB_CD@/global/config.dtd (%chrome/global/config.dtd)
|
||||
locale/@AB_CD@/global/config.properties (%chrome/global/config.properties)
|
||||
locale/@AB_CD@/global/console.dtd (%chrome/global/console.dtd)
|
||||
locale/@AB_CD@/global/console.properties (%chrome/global/console.properties)
|
||||
locale/@AB_CD@/global/contentAreaCommands.properties (%chrome/global/contentAreaCommands.properties)
|
||||
#ifndef MOZ_FENNEC
|
||||
locale/@AB_CD@/global/customizeToolbar.dtd (%chrome/global/customizeToolbar.dtd)
|
||||
|
Before Width: | Height: | Size: 659 B |
@ -1,156 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ===== console.css ====================================================
|
||||
== Styles used by the Error Console window.
|
||||
====================================================================== */
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
.console-box {
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
||||
/* ::::: console rows ::::: */
|
||||
|
||||
.console-row {
|
||||
border-bottom: 1px dotted threedshadow;
|
||||
padding: 4px 0px;
|
||||
}
|
||||
|
||||
.console-row-icon {
|
||||
padding: 4px;
|
||||
padding-inline-start: 5px;
|
||||
-moz-box-align: start !important;
|
||||
}
|
||||
|
||||
.console-row-msg > label:first-child,
|
||||
.console-row-file > label:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-time {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.console-icon {
|
||||
list-style-image: inherit;
|
||||
}
|
||||
|
||||
.console-error-msg {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ..... error rows ..... */
|
||||
|
||||
.console-row-code {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-inline-start: 3px;
|
||||
padding-inline-end: 0px;
|
||||
color: #0000BB;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.console-dots,
|
||||
.console-caret {
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
.console-dots {
|
||||
background: url("chrome://global/skin/console/console-error-dash.gif") repeat-x top;
|
||||
}
|
||||
|
||||
.console-caret {
|
||||
width: 7px;
|
||||
background: url("chrome://global/skin/console/console-error-caret.gif") no-repeat top;
|
||||
}
|
||||
|
||||
/* ..... message rows ..... */
|
||||
|
||||
.console-row[type="message"] {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ..... selected state ..... */
|
||||
|
||||
.console-row[selected="true"] {
|
||||
background-image: url("chrome://global/skin/console/itemSelected.png");
|
||||
}
|
||||
|
||||
.console-row-code[selected="true"],
|
||||
.console-row-content[selected="true"] > .console-row-file > .console-error-source > .text-link {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* ::::: icons ::::: */
|
||||
|
||||
.console-row[type="error"],
|
||||
.console-row[type="exception"] {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
|
||||
}
|
||||
|
||||
.console-row[type="error"] .console-row-msg,
|
||||
.console-row[type="exception"] .console-row-msg {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.console-row[type="warning"] {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=menu");
|
||||
}
|
||||
|
||||
.console-row[type="message"] {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-info?size=menu");
|
||||
}
|
||||
|
||||
/* ::::: toolbars ::::: */
|
||||
|
||||
#TextboxEval {
|
||||
margin: 2px !important;
|
||||
}
|
||||
|
||||
#ButtonEval {
|
||||
margin-top: 2px !important;
|
||||
margin-bottom: 2px !important;
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: 2px !important;
|
||||
}
|
||||
|
||||
/* Toolbar icons */
|
||||
|
||||
toolbar#ToolbarMode toolbarbutton {
|
||||
-moz-box-orient: horizontal;
|
||||
}
|
||||
|
||||
#Console\:modeAll {
|
||||
list-style-image: url("chrome://global/skin/console/console-toolbar.png");
|
||||
}
|
||||
|
||||
#Console\:modeErrors {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-error?size=toolbar");
|
||||
}
|
||||
|
||||
#Console\:modeWarnings {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=toolbar");
|
||||
}
|
||||
|
||||
#Console\:modeMessages {
|
||||
list-style-image: url("moz-icon://stock/gtk-dialog-info?size=toolbar");
|
||||
}
|
||||
|
||||
#Console\:clear {
|
||||
list-style-image: url("moz-icon://stock/gtk-clear?size=toolbar");
|
||||
}
|
||||
|
||||
toolbar#ToolbarMode .toolbarbutton-text {
|
||||
padding-inline-end: 4px;
|
||||
}
|
||||
|
||||
/* ::::: Fix Error Console toolbar button text spacing ::::: */
|
||||
|
||||
.toolbarbutton-text {
|
||||
padding-inline-start: 0px;
|
||||
padding-inline-end: 5px;
|
||||
}
|
Before Width: | Height: | Size: 516 B |
@ -35,9 +35,6 @@ toolkit.jar:
|
||||
skin/classic/global/toolbarbutton.css
|
||||
skin/classic/global/tree.css
|
||||
skin/classic/global/alerts/alert.css (alerts/alert.css)
|
||||
skin/classic/global/console/console.css (console/console.css)
|
||||
skin/classic/global/console/console.png (console/console.png)
|
||||
skin/classic/global/console/console-toolbar.png (console/console-toolbar.png)
|
||||
skin/classic/global/dirListing/remote.png (dirListing/remote.png)
|
||||
|
||||
skin/classic/global/icons/Authentication.png (icons/Authentication.png)
|
||||
|
Before Width: | Height: | Size: 55 B |
Before Width: | Height: | Size: 48 B |
@ -1,165 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ===== console.css ====================================================
|
||||
== Styles used by the Error Console window.
|
||||
======================================================================= */
|
||||
|
||||
/* View buttons */
|
||||
@import "chrome://global/skin/viewbuttons.css";
|
||||
|
||||
%include ../shared.inc
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
.console-box {
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ::::: console rows ::::: */
|
||||
|
||||
.console-row {
|
||||
border-bottom: 1px solid #A3A3A3;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.console-row-file {
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
.console-row-msg > label:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.console-row-msg > label, .comsole-row-msg > description, .console-error-msg, .console-row-file, .console-row-code {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.console-row-file > label {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.console-msg-text {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
.console-icon {
|
||||
list-style-image: inherit;
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
/* ..... error rows ..... */
|
||||
|
||||
.console-row-code {
|
||||
color: #0000BB;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.console-dots,
|
||||
.console-caret {
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
.console-dots {
|
||||
background: url("chrome://global/skin/console/console-error-dash.gif") repeat-x top;
|
||||
}
|
||||
|
||||
.console-caret {
|
||||
width: 7px;
|
||||
background: url("chrome://global/skin/console/console-error-caret.gif") no-repeat top;
|
||||
}
|
||||
|
||||
/* ..... message rows ..... */
|
||||
|
||||
.console-row[type="message"] {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ..... selected state ..... */
|
||||
|
||||
.console-row[selected="true"] {
|
||||
background-color: #3D80DF !important;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.console-row-code[selected="true"],
|
||||
.console-row-content[selected="true"] > .console-row-file,
|
||||
.console-row-content[selected="true"] > .console-row-file > .console-error-source > .text-link {
|
||||
color: #FFF !important;
|
||||
}
|
||||
|
||||
/* ::::: row colors ::::: */
|
||||
|
||||
.console-row[type="error"],
|
||||
.console-row[type="exception"] {
|
||||
background-color: #FFD0DC;
|
||||
}
|
||||
|
||||
.console-row[type="warning"] {
|
||||
background-color: #F8F3CC;
|
||||
}
|
||||
|
||||
.console-row[type="message"] {
|
||||
background-color: #D3EDFF;
|
||||
}
|
||||
|
||||
/* ::::: toolbars ::::: */
|
||||
|
||||
#ToolbarEval {
|
||||
-moz-appearance: none;
|
||||
background: @scopeBarBackground@;
|
||||
border-bottom: @scopeBarSeparatorBorder@;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#ToolbarEval > label {
|
||||
font-weight: bold;
|
||||
color: @scopeBarTitleColor@;
|
||||
}
|
||||
|
||||
#TextfieldEval {
|
||||
margin: 2px !important;
|
||||
}
|
||||
|
||||
#ButtonEval {
|
||||
margin: 0 4px;
|
||||
padding: 1px 10px;
|
||||
-moz-appearance: none;
|
||||
border-radius: 10000px;
|
||||
border: @roundButtonBorder@;
|
||||
background: @roundButtonBackground@;
|
||||
box-shadow: @roundButtonShadow@;
|
||||
}
|
||||
|
||||
#ButtonEval:hover:active {
|
||||
text-shadow: @loweredShadow@;
|
||||
background: @roundButtonPressedBackground@;
|
||||
box-shadow: @roundButtonPressedShadow@;
|
||||
}
|
||||
|
||||
toolbarseparator {
|
||||
min-height: 1em;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
/* Toolbar icons */
|
||||
|
||||
#ToolbarMode {
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
|
||||
#ToolbarMode toolbarbutton > .toolbarbutton-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#Console\:clear {
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-align: center;
|
||||
-moz-appearance: toolbarbutton;
|
||||
font: menu;
|
||||
text-shadow: @loweredShadow@;
|
||||
margin: 4px 0 9px;
|
||||
padding: 0 1px;
|
||||
}
|
@ -74,9 +74,6 @@ toolkit.jar:
|
||||
skin/classic/global/arrow/panelarrow-vertical@2x.png (arrow/panelarrow-vertical@2x.png)
|
||||
skin/classic/global/checkbox/cbox-check.gif (checkbox/cbox-check.gif)
|
||||
skin/classic/global/checkbox/cbox-check-dis.gif (checkbox/cbox-check-dis.gif)
|
||||
skin/classic/global/console/console-error-caret.gif (console/console-error-caret.gif)
|
||||
skin/classic/global/console/console-error-dash.gif (console/console-error-dash.gif)
|
||||
* skin/classic/global/console/console.css (console/console.css)
|
||||
skin/classic/global/dirListing/dirListing.css (dirListing/dirListing.css)
|
||||
skin/classic/global/dirListing/folder.png (dirListing/folder.png)
|
||||
skin/classic/global/dirListing/remote.png (dirListing/remote.png)
|
||||
|
@ -53,9 +53,6 @@
|
||||
|
||||
skin/classic/global/checkbox/cbox-check.gif (../../windows/global/checkbox/cbox-check.gif)
|
||||
skin/classic/global/checkbox/cbox-check-dis.gif (../../windows/global/checkbox/cbox-check-dis.gif)
|
||||
skin/classic/global/console/console-error-caret.gif (../../windows/global/console/console-error-caret.gif)
|
||||
skin/classic/global/console/console-error-dash.gif (../../windows/global/console/console-error-dash.gif)
|
||||
skin/classic/global/console/itemSelected.png (../../windows/global/console/itemSelected.png)
|
||||
* skin/classic/global/dirListing/dirListing.css (../../windows/global/dirListing/dirListing.css)
|
||||
skin/classic/global/dirListing/folder.png (../../windows/global/dirListing/folder.png)
|
||||
skin/classic/global/dirListing/local.png (../../windows/global/dirListing/local.png)
|
||||
|
Before Width: | Height: | Size: 55 B |
Before Width: | Height: | Size: 48 B |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 5.7 KiB |
@ -1,220 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ===== console.css ====================================================
|
||||
== Styles used by the Error Console window.
|
||||
====================================================================== */
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
.console-box {
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
||||
/* ::::: console rows ::::: */
|
||||
|
||||
.console-row {
|
||||
border-bottom: 1px solid ThreeDLightShadow;
|
||||
padding: 4px 0px;
|
||||
}
|
||||
|
||||
.console-row-icon {
|
||||
padding: 4px;
|
||||
padding-inline-start: 5px;
|
||||
-moz-box-align: start !important;
|
||||
}
|
||||
|
||||
.console-row-msg > label:first-child,
|
||||
.console-row-file > label:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-time {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.console-icon {
|
||||
list-style-image: inherit;
|
||||
}
|
||||
|
||||
.console-error-msg {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ..... error rows ..... */
|
||||
|
||||
.console-row-code {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-inline-start: 3px;
|
||||
padding-inline-end: 0px;
|
||||
color: #0000BB;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.console-dots,
|
||||
.console-caret {
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
.console-dots {
|
||||
background: url("chrome://global/skin/console/console-error-dash.gif") repeat-x top;
|
||||
}
|
||||
|
||||
.console-caret {
|
||||
width: 7px;
|
||||
background: url("chrome://global/skin/console/console-error-caret.gif") no-repeat top;
|
||||
}
|
||||
|
||||
/* ..... message rows ..... */
|
||||
|
||||
.console-row[type="message"] {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ..... selected state ..... */
|
||||
|
||||
.console-row[selected="true"] {
|
||||
background-image: url("chrome://global/skin/console/itemSelected.png");
|
||||
}
|
||||
|
||||
.console-row-code[selected="true"],
|
||||
.console-row-content[selected="true"] > .console-row-file > .console-error-source > .text-link {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* ::::: icons ::::: */
|
||||
|
||||
.console-row[type="error"],
|
||||
.console-row[type="exception"] {
|
||||
list-style-image: url("chrome://global/skin/icons/error-16.png");
|
||||
}
|
||||
|
||||
.console-row[type="error"] .console-row-msg,
|
||||
.console-row[type="exception"] .console-row-msg {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.console-row[type="warning"] {
|
||||
list-style-image: url("chrome://global/skin/icons/warning-16.png");
|
||||
}
|
||||
|
||||
.console-row[type="message"] {
|
||||
list-style-image: url("chrome://global/skin/icons/information-16.png");
|
||||
}
|
||||
|
||||
/* ::::: toolbars ::::: */
|
||||
|
||||
#TextboxEval {
|
||||
margin: 2px !important;
|
||||
}
|
||||
|
||||
#ButtonEval {
|
||||
margin-top: 2px !important;
|
||||
margin-bottom: 2px !important;
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: 2px !important;
|
||||
}
|
||||
|
||||
toolbarseparator {
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
/* Toolbar icons */
|
||||
|
||||
#ToolbarMode toolbarbutton {
|
||||
min-width: 57px;
|
||||
padding: 4px !important;
|
||||
}
|
||||
|
||||
toolbar#ToolbarMode toolbarbutton:active,
|
||||
toolbar#ToolbarMode toolbarbutton[checked="true"] {
|
||||
padding-inline-start: 5px !important;
|
||||
padding-inline-end: 3px !important;
|
||||
}
|
||||
|
||||
|
||||
toolbar#ToolbarMode toolbarbutton {
|
||||
list-style-image: url("chrome://global/skin/console/console-toolbar.png");
|
||||
-moz-box-orient: horizontal;
|
||||
padding: 4px !important;
|
||||
}
|
||||
|
||||
#Console\:modeAll {
|
||||
-moz-image-region: rect(0px 24px 24px 0px);
|
||||
}
|
||||
|
||||
#Console\:modeAll {
|
||||
-moz-image-region: rect(0px 24px 24px 0px);
|
||||
}
|
||||
|
||||
#Console\:modeAll:hover,
|
||||
#Console\:modeAll[checked="true"] {
|
||||
-moz-image-region: rect(24px 24px 48px 0px);
|
||||
}
|
||||
|
||||
#Console\:modeErrors {
|
||||
-moz-image-region: rect(0px 96px 24px 72px);
|
||||
}
|
||||
|
||||
#Console\:modeErrors:hover,
|
||||
#Console\:modeErrors[checked="true"] {
|
||||
-moz-image-region: rect(24px 96px 48px 72px);
|
||||
}
|
||||
|
||||
#Console\:modeWarnings {
|
||||
-moz-image-region: rect(0px 72px 24px 48px);
|
||||
}
|
||||
|
||||
#Console\:modeWarnings:hover,
|
||||
#Console\:modeWarnings[checked="true"] {
|
||||
-moz-image-region: rect(24px 72px 48px 48px);
|
||||
}
|
||||
|
||||
#Console\:modeMessages {
|
||||
-moz-image-region: rect(0px 48px 24px 24px);
|
||||
}
|
||||
|
||||
#Console\:modeMessages:hover,
|
||||
#Console\:modeMessages[checked="true"] {
|
||||
-moz-image-region: rect(24px 48px 48px 24px);
|
||||
}
|
||||
|
||||
#Console\:clear {
|
||||
-moz-image-region: rect(0px 120px 24px 96px);
|
||||
}
|
||||
|
||||
#Console\:clear:hover,
|
||||
#Console\:clear[checked="true"] {
|
||||
-moz-image-region: rect(24px 120px 48px 96px);
|
||||
}
|
||||
|
||||
toolbar#ToolbarMode .toolbarbutton-icon {
|
||||
padding: 2px 0 !important;
|
||||
}
|
||||
|
||||
toolbar#ToolbarMode .toolbarbutton-text {
|
||||
padding-inline-end: 4px;
|
||||
}
|
||||
|
||||
|
||||
/* ::::: Fix Error Console toolbar button text spacing ::::: */
|
||||
|
||||
.toolbarbutton-text {
|
||||
padding-inline-start: 0px;
|
||||
padding-inline-end: 5px;
|
||||
}
|
||||
|
||||
%ifdef XP_WIN
|
||||
@media not all and (-moz-os-version: windows-xp) {
|
||||
#ToolbarMode {
|
||||
-moz-appearance: -moz-win-browsertabbar-toolbox;
|
||||
}
|
||||
|
||||
#ToolbarEval {
|
||||
-moz-appearance: toolbox;
|
||||
}
|
||||
}
|
||||
%endif
|
Before Width: | Height: | Size: 459 B |
@ -37,8 +37,6 @@ toolkit.jar:
|
||||
skin/classic/global/toolbarbutton.css
|
||||
* skin/classic/global/tree.css
|
||||
skin/classic/global/alerts/alert.css (alerts/alert.css)
|
||||
* skin/classic/global/console/console.css (console/console.css)
|
||||
skin/classic/global/console/console-toolbar.png (console/console-toolbar.png)
|
||||
skin/classic/global/dirListing/remote.png (dirListing/remote.png)
|
||||
skin/classic/global/icons/autocomplete-search.svg (icons/autocomplete-search.svg)
|
||||
skin/classic/global/icons/blacklist_favicon.png (icons/blacklist_favicon.png)
|
||||
@ -55,7 +53,6 @@ toolkit.jar:
|
||||
* skin/classic/global/in-content/info-pages.css (in-content/info-pages.css)
|
||||
skin/classic/global/toolbar/spring.png (toolbar/spring.png)
|
||||
|
||||
skin/classic/global/console/console-toolbar-XP.png (console/console-toolbar-XP.png)
|
||||
skin/classic/global/dirListing/folder-XP.png (dirListing/folder-XP.png)
|
||||
skin/classic/global/dirListing/local-XP.png (dirListing/local-XP.png)
|
||||
skin/classic/global/dirListing/remote-XP.png (dirListing/remote-XP.png)
|
||||
@ -101,7 +98,6 @@ toolkit.jar:
|
||||
#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES
|
||||
[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
|
||||
#endif
|
||||
% override chrome://global/skin/console/console-toolbar.png chrome://global/skin/console/console-toolbar-XP.png osversion<6
|
||||
% override chrome://global/skin/dirListing/folder.png chrome://global/skin/dirListing/folder-XP.png osversion<6
|
||||
% override chrome://global/skin/dirListing/local.png chrome://global/skin/dirListing/local-XP.png osversion<6
|
||||
% override chrome://global/skin/dirListing/remote.png chrome://global/skin/dirListing/remote-XP.png osversion<6
|
||||
|