This commit is contained in:
Richard Newman 2012-10-08 17:08:01 -07:00
Родитель 783bc62129 5e5b54cfae
Коммит 63b2c29f6e
971 изменённых файлов: 273008 добавлений и 12566 удалений

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

@ -87,3 +87,4 @@ b6627f28b7ec17e1b46a594df0f780d3a40847e4 FIREFOX_AURORA_13_BASE
9697eadafa13b4e9233b39aaeecfeac79503cb54 FIREFOX_AURORA_16_BASE
6fdf9985acfe6f939da584b2559464ab22264fe7 FIREFOX_AURORA_16_BASE
fd72dbbd692012224145be1bf13df1d7675fd277 FIREFOX_AURORA_17_BASE
2704e441363fe2a48e992dfac694482dfd82664a FIREFOX_AURORA_18_BASE

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

@ -46,7 +46,9 @@ function virtualCursorControl(aMessage) {
moved = vc[details.action](rule);
}
} catch (x) {
moved = vc.moveNext(rule, content.document.activeElement, true);
let acc = Utils.AccRetrieval.
getAccessibleFor(content.document.activeElement);
moved = vc.moveNext(rule, acc, true);
}
break;
case 'moveToPoint':
@ -88,7 +90,7 @@ function forwardMessage(aVirtualCursor, aMessage) {
return true;
}
} catch (x) {
Logger.error(x);
// Frame may be hidden, we regard this case as false.
}
return false;
}

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

@ -5,7 +5,7 @@
MOZ_APP_BASENAME=B2G
MOZ_APP_VENDOR=Mozilla
MOZ_APP_VERSION=18.0a1
MOZ_APP_VERSION=19.0a1
MOZ_APP_UA_NAME=Firefox
MOZ_UA_OS_AGNOSTIC=1

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

@ -1,5 +1,6 @@
resource testpilot ./
content testpilot content/
locale testpilot en-US locale/en-US/
skin testpilot skin skin/all/
skin testpilot-os skin skin/linux/ os=Linux
skin testpilot-os skin skin/linux/ os=SunOS
@ -10,6 +11,7 @@ overlay chrome://browser/content/macBrowserOverlay.xul chrome://testpilot/conten
overlay chrome://browser/content/browser.xul chrome://testpilot/content/browser.xul
style chrome://global/content/customizeToolbar.xul chrome://testpilot/content/browser.css
# For the menubar on Mac
overlay chrome://testpilot/content/all-studies-window.xul chrome://browser/content/macBrowserOverlay.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} os=Darwin

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

@ -23,9 +23,8 @@
-moz-padding-start: 5px;
}
#pilot-notifications-button {
margin-right: 10px;
#tp-notification-popup-icon {
list-style-image: url("chrome://testpilot/skin/testpilot_16x16.png");
}
/* Popup Bounding Box */
@ -36,25 +35,15 @@
margin-top: -6px;
margin-right: -3px;
width: 480px;
/* FIXES: #725850 based on #717262
/* Needed whilst we support Gecko < 13 */
border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 56 22 18 / 26px 56px 22px 18px round stretch;
/* Supported in Gecko >= 13 */
border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 50 22 18 fill stretch;
border-width: 26px 56px 22px 18px;
border-style: solid;
}
.tail-up {
border-width: 26px 56px 22px 18px;
border-style: solid;
-moz-border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 56 22 18 fill round stretch;
}
/* tail-down uses the old styling; it doesn't look as good as the new styling,
but the new styling doesn't work on 3.6.
TODO: If someone is using 3.7.* or 4.* but is NOT on the beta channel and
installed Test Pilot from AMO, they should get the new styling, similar
to .tail-up! */
.tail-down {
border-width: 26px 50px 22px 18px;
border-style: solid;
-moz-border-image: url(chrome://testpilot/skin/notification-tail-down.png) 26 50 22 18 fill repeat;
color: white;
}
.pilot-notification-popup-container {
-moz-appearance: none;
@ -67,9 +56,14 @@
margin-bottom: 12px;
}
#pilot-notification-text,
#pilot-notification-text {
margin-bottom: 5px;
}
#pilot-notification-link {
margin-bottom: 5px;
text-decoration: underline;
cursor: pointer;
}
#pilot-notification-close {
@ -240,11 +234,6 @@ image.results-thumbnail {
margin: 10px;
}
.notification-link {
text-decoration: underline;
cursor: pointer;
}
prefpane .groupbox-body {
-moz-appearance: none;
padding: 8px 4px 4px 4px;

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

@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://testpilot-os/skin/feedback.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">

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

@ -1,13 +1,28 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<!-- 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 HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Test Pilot Debug Page</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script src="experiment-page.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
var numstartupattempts=0;
function startup() {
numstartupattempts += 1;
dump('numattempts: ' + numstartupattempts + "\n");
var dropsuccess = populateFileDropdown();
if (! dropsuccess) {
$('h1#status').fadeIn('slow','linear');
window.setTimeout(function() {startup()}, 2000); // try again!
} else {
showSelectedTaskStatus()
showIndexFileDropdown();
$('h1#status').fadeOut("slow", "linear");
};
};
function getEid() {
var selector = document.getElementById("task-selector");
var i = selector.selectedIndex;
@ -30,7 +45,7 @@
if (errors.length > 0) {
str = "<ul>";
for each (let errStr in errors) {
str += "<li>" + errStr + "</li>";
str += "<li>" + errStr + "</li>";
}
str += "</ul>";
} else {
@ -62,7 +77,7 @@
[jarStore, loader.fs])});
dump("Debug page Requiring toolbar study.\n");
var toolbarStudy = clientLoader.require("toolbar-study");
}
function remindMe() {
@ -74,6 +89,7 @@
function getCodeStorage() {
Components.utils.import("resource://testpilot/modules/setup.js");
var loader = TestPilotSetup._remoteExperimentLoader;
if (! loader) {return undefined;}
return loader._jarStore;
}
@ -136,6 +152,8 @@
function populateFileDropdown() {
var codeStore = getCodeStorage();
if (! codeStore ) { return false }
var files = codeStore.listAllFiles();
var selector = document.getElementById("file-selector");
var opt, i;
@ -155,6 +173,7 @@
opt.setAttribute("value", tasks[i].id);
selector.appendChild(opt);
}
return true;
}
function showSelectedTaskStatus() {
@ -216,6 +235,9 @@
}
}
$(function(){startup()});
</script>
<style type="text/css">
@ -224,7 +246,8 @@
</head>
<body onload="populateFileDropdown();showSelectedTaskStatus();showIndexFileDropdown();">
<body>
<h1 id='status'><blink>loading experiments</blink></h1> <!-- take that, web standards! -->
<fieldset>
<p><select id="task-selector" onchange="showSelectedTaskStatus();"></select> Current Status = <span id="show-status-span"></span>.
@ -252,7 +275,7 @@ or set it to
<button onclick="reloadAllExperiments();">Reload All Experiments</button>
<button onclick="remindMe();">Notify Me</button>
<button onclick="testJarStore();">Test Jar Store</button>
Index file:
Index file:
<select id="index-file-selector" onchange="setSelectedIndexFile();">
<option value="index.json">index.json</option>
<option value="index-dev.json">index-dev.json</option>

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

@ -158,7 +158,7 @@ var stringBundle;
}
// write, create, truncate
foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();

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

@ -0,0 +1,428 @@
/* 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 PAGE_TYPE_STATUS = 0;
const PAGE_TYPE_QUIT = 1;
var stringBundle;
function showRawData(experimentId) {
window.openDialog(
"chrome://testpilot/content/raw-data-dialog.xul",
"TestPilotRawDataDialog", "chrome,centerscreen,resizable,scrollbars",
experimentId);
}
function getUrlParam(name) {
// from http://www.netlobo.com/url_query_string_javascript.html
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if( results == null )
return "";
else
return results[1];
}
function uploadData() {
Components.utils.import("resource://testpilot/modules/setup.js");
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
// If always-submit-checkbox is checked, set the pref
if (task._recursAutomatically) {
let checkBox = document.getElementById("always-submit-checkbox");
if (checkBox && checkBox.checked) {
task.setRecurPref(TaskConstants.ALWAYS_SUBMIT);
}
}
// Study web content must provide an element with id 'upload-status'.
// Fill it first with a message about data being uploaded; if there's
// an error, replace it with the error message.
let uploadStatus = document.getElementById("upload-status");
uploadStatus.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.uploadingData");
task.upload( function(success) {
if (success) {
window.location =
"chrome://testpilot/content/status.html?eid=" + eid;
} else {
// Replace 'now uploading' message
let errorParagraph = document.createElement("p");
errorParagraph.innerHTML = stringBundle.GetStringFromName("testpilot.statusPage.uploadErrorMsg");
let willRetryParagraph = document.createElement("p");
willRetryParagraph.innerHTML = stringBundle.GetStringFromName("testpilot.statusPage.willRetry");
uploadStatus.innerHTML = "";
uploadStatus.appendChild(errorParagraph);
uploadStatus.appendChild(willRetryParagraph);
}
});
}
function deleteData() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
task.dataStore.wipeAllData();
// reload the URL after wiping all data.
window.location = "chrome://testpilot/content/status.html?eid=" + eid;
}
function saveCanvas(canvas) {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let fp = Components.classes["@mozilla.org/filepicker;1"].
createInstance(nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == nsIFilePicker.returnOK ||
aResult == nsIFilePicker.returnReplace) {
const nsIWebBrowserPersist =
Components.interfaces.nsIWebBrowserPersist;
let file = fp.file;
// create a data url from the canvas and then create URIs of the
// source and targets
let io = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
let source = io.newURI(canvas.toDataURL("image/png"), "UTF8", null);
let target = io.newFileURI(file);
// prepare to save the canvas data
let persist = Components.classes[
"@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
createInstance(nsIWebBrowserPersist);
persist.persistFlags = nsIWebBrowserPersist.
PERSIST_FLAGS_REPLACE_EXISTING_FILES;
persist.persistFlags |= nsIWebBrowserPersist.
PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
// displays a download dialog (remove these 3 lines for silent download)
let xfer = Components.classes["@mozilla.org/transfer;1"].
createInstance(Components.interfaces.nsITransfer);
xfer.init(source, target, "", null, null, null, persist, false);
persist.progressListener = xfer;
// save the canvas data to the file
persist.saveURI(source, null, null, null, null, file, null);
}
};
fp.init(window, null, nsIFilePicker.modeSave);
fp.appendFilters(nsIFilePicker.filterImages | nsIFilePicker.filterAll);
fp.defaultString = "canvas.png";
fp.open(fpCallback);
}
function exportData() {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
let fp = Components.classes["@mozilla.org/filepicker;1"].
createInstance(nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == nsIFilePicker.returnOK ||
aResult == nsIFilePicker.returnReplace) {
const nsIWebBrowserPersist =
Components.interfaces.nsIWebBrowserPersist;
let foStream =
Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
let converter =
Components.classes["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Components.interfaces.nsIConverterOutputStream);
let file = fp.file;
let dataStore = task.dataStore;
let columnNames = dataStore.getHumanReadableColumnNames();
let propertyNames = dataStore.getPropertyNames();
let csvString = "";
// titles
for (let i = 0; i < columnNames.length; i++) {
csvString += "\"" + columnNames[i] + "\",";
}
if (csvString.length > 0) {
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
dataStore.getAllDataAsJSON(true, function(rawData) {
// data
for (let i = 0; i < rawData.length; i++) {
for (let j = 0; j < columnNames.length; j++) {
csvString += "\"" + rawData[i][propertyNames[j]] + "\",";
}
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
// write, create, truncate
foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();
});
}
};
fp.init(window, null, nsIFilePicker.modeSave);
fp.appendFilters(nsIFilePicker.filterImages | nsIFilePicker.filterAll);
fp.defaultString = task.title + ".csv";
fp.open(fpCallback);
}
function openLink(url) {
// open the link in the chromeless window
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow) {
recentWindow.TestPilotWindowUtils.openInTab(url);
} else {
window.open(url);
}
}
function getTestEndingDate(experimentId) {
Components.utils.import("resource://testpilot/modules/setup.js");
var task = TestPilotSetup.getTaskById(experimentId);
var endDate = new Date(task.endDate);
var diff = (endDate - Date.now());
var span = document.getElementById("test-end-time");
if (!span) {
return;
}
if (diff < 0) {
span.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.endedAlready");
return;
}
var hours = diff / (60 * 60 * 1000);
if (hours < 24) {
span.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.todayAt", [endDate.toLocaleTimeString()], 1);
} else {
span.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.endOn", [endDate.toLocaleString()], 1);
}
}
function showMetaData() {
Components.utils.import("resource://testpilot/modules/metadata.js");
Components.utils.import("resource://gre/modules/PluralForm.jsm");
MetadataCollector.getMetadata(function(md) {
var mdLocale = document.getElementById("md-locale");
if (mdLocale)
mdLocale.innerHTML = md.location;
var mdVersion = document.getElementById("md-version");
if (mdVersion)
mdVersion.innerHTML = md.version;
var mdOs = document.getElementById("md-os");
if (mdOs)
mdOs.innerHTML = md.operatingSystem;
var mdNumExt = document.getElementById("md-num-ext");
if (mdNumExt) {
// This computes the correctly localized singular or plural string
// of the number of extensions, e.g. "1 extension", "2 extensions", etc.
let str = stringBundle.GetStringFromName("testpilot.statusPage.numExtensions");
var numExt = md.extensions.length;
mdNumExt.innerHTML = PluralForm.get(numExt, str).replace("#1", numExt);
}
});
}
function onQuitPageLoad() {
Components.utils.import("resource://testpilot/modules/setup.js");
setStrings(PAGE_TYPE_QUIT);
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
let header = document.getElementById("about-quit-title");
header.innerHTML =
stringBundle.formatStringFromName(
"testpilot.quitPage.aboutToQuit", [task.title], 1);
if (task._recursAutomatically) {
document.getElementById("recur-options").setAttribute("style", "");
document.getElementById("recur-checkbox-container").
setAttribute("style", "");
}
}
function quitExperiment() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
let eid = getUrlParam("eid");
let reason = document.getElementById("reason-for-quit").value;
let task = TestPilotSetup.getTaskById(eid);
task.optOut(reason, function(success) {
// load the you-are-canceleed page.
window.location = "chrome://testpilot/content/status.html?eid=" + eid;
});
// If opt-out-forever checkbox is checked, opt out forever!
if (task._recursAutomatically) {
let checkBox = document.getElementById("opt-out-forever");
if (checkBox.checked) {
task.setRecurPref(TaskConstants.NEVER_SUBMIT);
}
// quit test so rescheduling
task._reschedule();
}
}
function updateRecurSettings() {
Components.utils.import("resource://testpilot/modules/setup.js");
let eid = getUrlParam("eid");
let experiment = TestPilotSetup.getTaskById(eid);
let recurSelector = document.getElementById("recur-selector");
let newValue = recurSelector.options[recurSelector.selectedIndex].value;
experiment.setRecurPref(parseInt(newValue));
}
function showRecurControls(experiment) {
Components.utils.import("resource://testpilot/modules/tasks.js");
let recurPrefSpan = document.getElementById("recur-pref");
if (!recurPrefSpan) {
return;
}
let days = experiment._recurrenceInterval;
recurPrefSpan.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.recursEveryNumberOfDays", [days], 1);
let controls = document.getElementById("recur-controls");
let selector = document.createElement("select");
controls.appendChild(selector);
selector.setAttribute("onchange", "updateRecurSettings();");
selector.setAttribute("id", "recur-selector");
let option = document.createElement("option");
option.setAttribute("value", TaskConstants.ASK_EACH_TIME);
if (experiment.recurPref == TaskConstants.ASK_EACH_TIME) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.askMeBeforeSubmitData");
selector.appendChild(option);
option = document.createElement("option");
option.setAttribute("value", TaskConstants.ALWAYS_SUBMIT);
if (experiment.recurPref == TaskConstants.ALWAYS_SUBMIT) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.alwaysSubmitData");
selector.appendChild(option);
option = document.createElement("option");
option.setAttribute("value", TaskConstants.NEVER_SUBMIT);
if (experiment.recurPref == TaskConstants.NEVER_SUBMIT) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.neverSubmitData");
selector.appendChild(option);
}
function loadExperimentPage() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
var contentDiv = document.getElementById("experiment-specific-text");
var dataPrivacyDiv = document.getElementById("data-privacy-text");
// Get experimentID from the GET args of page
var eid = getUrlParam("eid");
var experiment = TestPilotSetup.getTaskById(eid);
if (!experiment) {
// Possible that experiments aren't done loading yet. Try again in
// a few seconds.
contentDiv.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.loading");
window.setTimeout(function() { loadExperimentPage(); }, 2000);
return;
}
// Let the experiment fill in its web content (asynchronous)
experiment.getWebContent(function(webContent) {
contentDiv.innerHTML = webContent;
// Metadata and start/end date should be filled in for every experiment:
showMetaData();
getTestEndingDate(eid);
if (experiment._recursAutomatically &&
experiment.status != TaskConstants.STATUS_FINISHED) {
showRecurControls(experiment);
}
// Do whatever the experiment's web content wants done on load
// (Usually drawing a graph) - must be done after innerHTML is set.
experiment.webContent.onPageLoad(experiment, document, jQuery);
});
experiment.getDataPrivacyContent(function(dataPrivacyContent) {
if (dataPrivacyContent && dataPrivacyContent.length > 0) {
dataPrivacyDiv.innerHTML = dataPrivacyContent;
dataPrivacyDiv.removeAttribute("hidden");
}
});
}
function onStatusPageLoad() {
setStrings(PAGE_TYPE_STATUS);
/* An experiment ID (eid) must be provided in the url params. Show status
* for that experiment.*/
loadExperimentPage();
}
function setStrings(pageType) {
stringBundle =
Components.classes["@mozilla.org/intl/stringbundle;1"].
getService(Components.interfaces.nsIStringBundleService).
createBundle("chrome://testpilot/locale/main.properties");
let map;
let mapLength;
if (pageType == PAGE_TYPE_STATUS) {
map = [
{ id: "page-title", stringKey: "testpilot.fullBrandName" },
{ id: "comments-and-discussions-link",
stringKey: "testpilot.page.commentsAndDiscussions" },
{ id: "propose-test-link",
stringKey: "testpilot.page.proposeATest" },
{ id: "testpilot-twitter-link",
stringKey: "testpilot.page.testpilotOnTwitter" }
];
} else if (pageType == PAGE_TYPE_QUIT) {
map = [
{ id: "page-title", stringKey: "testpilot.fullBrandName" },
{ id: "comments-and-discussions-link",
stringKey: "testpilot.page.commentsAndDiscussions" },
{ id: "propose-test-link",
stringKey: "testpilot.page.proposeATest" },
{ id: "testpilot-twitter-link",
stringKey: "testpilot.page.testpilotOnTwitter" },
{ id: "optional-message",
stringKey: "testpilot.quitPage.optionalMessage" },
{ id: "reason-text",
stringKey: "testpilot.quitPage.reason" },
{ id: "recur-options",
stringKey: "testpilot.quitPage.recurringStudy" },
{ id: "quit-forever-text",
stringKey: "testpilot.quitPage.quitForever" },
{ id: "quit-study-link",
stringKey: "testpilot.quitPage.quitStudy" }
];
}
mapLength = map.length;
for (let i = 0; i < mapLength; i++) {
let entry = map[i];
document.getElementById(entry.id).innerHTML =
stringBundle.GetStringFromName(entry.stringKey);
}
}

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

@ -0,0 +1,21 @@
--- content/experiment-page.js
+++ content/experiment-page.js
@@ -156,17 +156,17 @@
for (let j = 0; j < columnNames.length; j++) {
csvString += "\"" + rawData[i][propertyNames[j]] + "\",";
}
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
// write, create, truncate
- foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
+ foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();
});
}
}
function openLink(url) {

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

@ -3,9 +3,6 @@
- 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://testpilot/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://testpilot-os/skin/feedback.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>
<bindings id="popupBindings"
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="testpilot-notification"
extends="chrome://global/content/bindings/notification.xml#popup-notification">
<!-- Remember to keep this up to date with the changes to base binding
at http://hg.mozilla.org/mozilla-central/file/tip/toolkit/content/widgets/notification.xml-->
<content align="start">
<xul:image class="popup-notification-icon" anonid="notification-icon"
xbl:inherits="popupid"/>
<xul:vbox flex="1">
<xul:label anonid="notification-title" class="testpilot-notification-title"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label"/>
<xul:spacer flex="1"/>
<xul:hbox class="popup-notification-button-container"
pack="end" align="center">
<xul:button anonid="button"
class="popup-notification-menubutton"
type="menu-button"
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
<xul:menupopup anonid="menupopup"
xbl:inherits="oncommand=menucommand">
<children/>
<xul:menuitem class="menuitem-iconic popup-notification-closeitem"
label="&closeNotificationItem.label;"
xbl:inherits="oncommand=closeitemcommand"/>
</xul:menupopup>
</xul:button>
</xul:hbox>
</xul:vbox>
<xul:vbox pack="start">
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton popup-notification-closebutton tabbable"
xbl:inherits="oncommand=closebuttoncommand"
tooltiptext="&closeNotification.tooltip;"/>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
this.title.value = this.notification.options.title;
this.icon.classList.add(this.notification.options.iconClass);
this.closebutton.addEventListener("command",
this.notification.options.closeButtonFunc,
false);
]]>
</constructor>
<field name="title" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "notification-title");
</field>
<field name="icon" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "notification-icon");
</field>
</implementation>
</binding>
</bindings>

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

@ -3,51 +3,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
padding-bottom: 0px;
padding-bottom: 0px;
padding-top: 20px;
padding-left: 0px;
padding-right: 0px;
}
body {
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Lucida, Arial, Helvetica, sans-serif;
font-family: 'DroidSans';
background: #fff url('chrome://testpilot/skin/bg.jpg') repeat-x top center;
padding: 0px;
padding: 0px;
color:#787878;
font-size:12px;
line-height: 18px;
margin: 0 auto;
}
h1 {font-family: 'DroidSans'; color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {font-family: 'DroidSans'; color: #54717b; font-size: 24px; line-height: 22px;font-weight: normal; letter-spacing: -1px}
h1 {font-family: 'sans-serif'; color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {font-family: 'sans-serif'; color: #54717b; font-size: 24px; line-height: 22px;font-weight: normal; letter-spacing: -1px}
p, ul {font-size: 12px;}
a:link, a:hover, a:active, a:focus, a:visited {color: #9f423b; text-decoration: none;}
a:hover {color: #9f423b; text-decoration: none;}
.bold {font-family: 'DroidSans-Bold'; color:#3a3a3a;}
.bold {color:#3a3a3a;}
.address {margin-left: 20px;}
.inactive {color:#ccc;}
li {list-style-type: circle;}
.spacer {height: 75px;}
@font-face{
font-family: 'DroidSans';
src: url('chrome://testpilot/skin/fonts/DroidSans.ttf') format('truetype');
}
@font-face{
font-family: 'DroidSans-Bold';
src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
}
#container {
margin: 0px auto;
width: 950px;
@ -61,14 +50,14 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
#contentWelcome {
margin-top: 120px;
padding: 8px 24px 24px 24px;
font-family: 'DroidSans';
font-family: 'sans-serif';
text-align: left;
}
#content {
margin-top: 60px;
padding: 8px 24px 24px 24px;
font-family: 'DroidSans';
font-family: 'sans-serif';
text-align: left;
}
@ -78,16 +67,6 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
margin-top: 240px;
}
.downloadButton {
margin-bottom: -10px;
margin-top: -2px;
margin-right: 12px;
}
.downloadH1 {font-family: 'DroidSans-bold'; color: #3a3a3a; font-size: 24px; font-weight: normal; letter-spacing: -1px; margin-right: 8px;}
.downloadH2 {font-family: 'DroidSans'; color: #3a3a3a; font-size: 22px; font-weight: normal; letter-spacing: -1px; line-height: 28px; margin-left: 46px;}
#intro {
float: left;
width: 500px;
@ -102,60 +81,60 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
}
.button {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 12px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 12px;
background-color: #e7eaec;
//display: inline;
/*display: inline;*/
}
.home_button {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 12px;
width: 240px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 12px;
background-color: #e7eaec;
//display: inline;
/*display: inline;*/
}
.callout {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 24px;
margin: 24px auto;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 12px;
//display: inline;
/*display: inline;*/
}
.home_callout {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
vertical-align: middle;
width: 240px;
@ -164,11 +143,11 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 12px;
//display: inline;
/*display: inline;*/
}
.homeIcon {
@ -189,7 +168,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
margin-right: 6px;
}
/* ------- MENU -------
/* ------- MENU -------
#menu {
margin: 20px auto;
@ -204,7 +183,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
border-bottom: 3px solid #adb6ba;
-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
background-color: #fff;
}
}
*/
@ -222,7 +201,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
font-size: 14px;
text-shadow: 1px 1px 1px rgba(173, 182, 186, 1);
background-color: rgba(173, 182, 186, 0.3);
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 -10px 12px;
padding: 9px 8px 8px 8px;
}

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

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<statusbar id="status-bar">
<statusbarpanel id="pilot-notifications-button"
class="statusbarpanel-iconic"
insertbefore="security-button"
onmousedown="
event.preventDefault();
TestPilotMenuUtils.onMenuButtonMouseDown();"
image="chrome://testpilot/skin/testpilot_16x16.png"/>
<panel id="pilot-notification-popup" hidden="true" noautofocus="true"
level="parent" position="after_start">
<vbox class="pilot-notification-popup-container">
<hbox class="pilot-notification-toprow">
<image id="pilot-notification-icon" />
<vbox pack="center">
<label id="pilot-notification-title" class="pilot-title" />
</vbox>
<spacer flex="1" />
<vbox pack="start">
<image id="pilot-notification-close"
tooltiptext="&testpilot.notification.close.tooltip;" />
</vbox>
</hbox>
<description id="pilot-notification-text" />
<hbox align="right"><label id="pilot-notification-link" /></hbox>
<hbox>
<checkbox id="pilot-notification-always-submit-checkbox"
label="&testpilot.settings.alwaysSubmitData.shortLabel;" />
<spacer flex="1" />
</hbox>
<hbox align="right">
<button id="pilot-notification-submit" />
</hbox>
</vbox>
</panel>
</statusbar>
</overlay>

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

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- anchor for the fx4 style notifications -->
<toolbar id="nav-bar">
<box id="tp-notification-popup-box" hidden="true" align="center">
<image id="tp-notification-popup-icon" class="notification-anchor-icon" role="button"/>
</box>
<panel id="testpilot-notification-popup" type="arrow" position="after_start"
hidden="true" orient="vertical" role="alert">
</panel>
</toolbar>
</overlay>

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

@ -54,15 +54,8 @@
</menupopup>
</menu>
<statusbar id="status-bar">
<statusbarpanel id="pilot-notifications-button"
class="statusbarpanel-iconic"
insertbefore="security-button"
onmousedown="
event.preventDefault();
TestPilotMenuUtils.onMenuButtonMouseDown();"
image="chrome://testpilot/skin/testpilot_16x16.png"/>
<toolbar id="nav-bar">
<image id="tp-notification-popup-icon" role="button" hidden="true"/>
<panel id="pilot-notification-popup" hidden="true" noautofocus="true"
level="parent" position="after_start">
<vbox class="pilot-notification-popup-container">
@ -78,7 +71,7 @@
</vbox>
</hbox>
<description id="pilot-notification-text" />
<hbox align="right"><label id="pilot-notification-link" /></hbox>
<hbox align="right"><label id="pilot-notification-link"/></hbox>
<hbox>
<checkbox id="pilot-notification-always-submit-checkbox"
label="&testpilot.settings.alwaysSubmitData.label;" />
@ -89,5 +82,5 @@
</hbox>
</vbox>
</panel>
</statusbar>
</toolbar>
</overlay>

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

@ -49,11 +49,11 @@ var TestPilotWelcomePage = {
{ id: "testpilot-addon-text",
stringKey: "testpilot.welcomePage.testpilotAddon" },
{ id: "icon-explanation-text",
stringKey: "testpilot.welcomePage.iconExplanation" },
stringKey: "testpilot.welcomePage.iconExplanation2" },
{ id: "icon-explanation-more-text",
stringKey: "testpilot.welcomePage.moreIconExplanation" },
stringKey: "testpilot.welcomePage.moreIconExplanation2" },
{ id: "notification-info-text",
stringKey: "testpilot.welcomePage.notificationInfo" },
stringKey: "testpilot.welcomePage.notificationInfo2" },
{ id: "privacy-policy-link",
stringKey: "testpilot.welcomePage.privacyPolicy" },
{ id: "legal-notices-link",

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

@ -10,11 +10,11 @@
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>testpilot@labs.mozilla.com</em:id>
<em:version>1.1.2</em:version>
<em:version>1.2.2</em:version>
<em:type>2</em:type>
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
@ -22,7 +22,7 @@
<em:maxVersion>@FIREFOX_VERSION@</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Feedback</em:name>
<em:description>Help make Firefox better by giving feedback.</em:description>

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

@ -0,0 +1,33 @@
<!ENTITY testpilot.brand.label "Test Pilot">
<!ENTITY testpilot.settings.label "Settings">
<!ENTITY testpilot.settings.dataSubmission.label "Data Submission">
<!ENTITY testpilot.settings.notifications.label "Notifications">
<!ENTITY testpilot.settings.notifyWhen.label "Notify me when…">
<!ENTITY testpilot.settings.readyToSubmit.label "A study is ready to submit">
<!ENTITY testpilot.settings.newStudy.label "There's a new study">
<!ENTITY testpilot.settings.hasNewResults.label "A study has new results">
<!ENTITY testpilot.settings.alwaysSubmitData.label "Automatically submit my data (don't ask me)">
<!ENTITY testpilot.allYourStudies.label "All Your User Studies…">
<!ENTITY testpilot.about.label "About Test Pilot">
<!-- all studies window -->
<!ENTITY testpilot.studiesWindow.title "Your Test Pilot Studies">
<!ENTITY testpilot.studiesWindow.currentStudies.label "Current Studies">
<!ENTITY testpilot.studiesWindow.finishedStudies.label "Finished Studies">
<!ENTITY testpilot.studiesWindow.studyFindings.label "Study Findings">
<!ENTITY testpilot.studiesWindow.settings.label "Settings">
<!ENTITY testpilot.studiesWindow.stillLoadingMessage "Loading, please wait…">
<!-- raw data dialog -->
<!ENTITY testpilot.rawDataWindow.title "Test Pilot: Raw Data">
<!-- notification -->
<!ENTITY testpilot.notification.close.tooltip "Close">
<!-- Firefox 4 beta version UI -->
<!ENTITY testpilot.enable.label "Turn On User Studies">
<!ENTITY testpilot.feedbackbutton.label "Feedback">
<!ENTITY testpilot.happy.label "Firefox Made Me Happy Because…">
<!ENTITY testpilot.sad.label "Firefox Made Me Sad Because…">
<!ENTITY testpilot.broken.label "Report this website as broken…">
<!ENTITY testpilot.idea.label "Give us a suggestion…">

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

@ -0,0 +1,98 @@
# description for add-on manager
extensions.testpilot@labs.mozilla.com.description = Help make Firefox better by running user studies.
# common
testpilot.fullBrandName = Mozilla Labs Test Pilot
testpilot.moreInfo = More Info
testpilot.submit = Submit
testpilot.takeSurvey = Take the Survey
# Feedback button menu
testpilot.turnOn = Turn On User Studies
testpilot.turnOff = Turn Off User Studies
# studies window
testpilot.studiesWindow.noStudies = We are working on a new study now; it will knock on your door soon! Stay Tuned!
testpilot.studiesWindow.uploading = Uploading…
testpilot.studiesWindow.unableToReachServer = Unable to reach Mozilla; please try again later.
testpilot.studiesWindow.thanksForContributing = Thanks for contributing!
testpilot.studiesWindow.finishedOn = Finished on %S
testpilot.studiesWindow.canceledStudy = (You canceled this study)
testpilot.studiesWindow.missedStudy = (You missed this study)
testpilot.studiesWindow.willStart = Will start on %S
testpilot.studiesWindow.gatheringData = Currently gathering data.
testpilot.studiesWindow.willFinish = Will finish on %S
testpilot.studiesWindow.proposeStudy = Propose your own study
# for pages
testpilot.page.commentsAndDiscussions = Comments & Discussions »
testpilot.page.proposeATest = Propose a Test »
testpilot.page.testpilotOnTwitter = @MozTestPilot on Twitter »
# status page
testpilot.statusPage.uploadingData = Now uploading data…
testpilot.statusPage.uploadErrorMsg = Oops! There was an error connecting to the Mozilla servers. Maybe your network connection is down?
testpilot.statusPage.willRetry = Test Pilot will retry automatically, so it's OK to close this page now.
testpilot.statusPage.endedAlready = (It has already ended and you should not be seeing this page)
testpilot.statusPage.todayAt = today, at %S
testpilot.statusPage.endOn = on %S
# LOCALIZATION NOTE (numExtensions): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 = number of extensions
# example: "2 extensions"
testpilot.statusPage.numExtensions = #1 extension;#1 extensions
testpilot.statusPage.recursEveryNumberOfDays = This test recurs every %S days. Each time it completes:
testpilot.statusPage.askMeBeforeSubmitData = Ask me whether I want to submit my data.
testpilot.statusPage.alwaysSubmitData = Always submit my data, and don't ask me about it.
testpilot.statusPage.neverSubmitData = Never submit my data, and don't ask me about it.
testpilot.statusPage.loading = Loading, please wait a moment…
# quit page
testpilot.quitPage.aboutToQuit = You are about to quit the "%S" study.
testpilot.quitPage.optionalMessage = (Optional) If you have a minute, please let us know why you have chosen to quit the study.
testpilot.quitPage.reason = Reason:
testpilot.quitPage.recurringStudy = This is a recurring study. Normally we will let you know the next time we run the study. If you never want to hear about this study again, check the box below:
testpilot.quitPage.quitForever = Quit this recurring study.
testpilot.quitPage.quitStudy = Quit the Study »
# welcome page
testpilot.welcomePage.thankYou = Thank You for Joining the Test Pilot Team!
testpilot.welcomePage.gettingStarted = Getting Started
testpilot.welcomePage.pleaseTake = Please take the
testpilot.welcomePage.backgroundSurvey = Pilot Background Survey
testpilot.welcomePage.clickToOpenStudiesWindow = Click here to see the studies that are currently running.
testpilot.welcomePage.testpilotAddon = Test Pilot Add-on
testpilot.welcomePage.iconExplanation2 = « look for this icon in the Tools menu to find the Test Pilot sub-menu.
testpilot.welcomePage.moreIconExplanation2 = Use the sub-menu to control settings and see what studies are running.
testpilot.welcomePage.notificationInfo2 = Test Pilot will pop up a notification when a study needs your attention.
testpilot.welcomePage.privacyPolicy = Privacy Policy
testpilot.welcomePage.legalNotices = Legal Notices
# survey page
testpilot.surveyPage.saveAnswers = Save Answers
testpilot.surveyPage.submitAnswers = Submit Answers
testpilot.surveyPage.changeAnswers = Change Answers
testpilot.surveyPage.loading = Loading, please wait a moment…
testpilot.surveyPage.thankYouForFinishingSurvey = Thank you for finishing this survey. Your answers will be uploaded along with the next set of experimental data.
testpilot.surveyPage.reviewOrChangeYourAnswers = If you would like to review or change your answers, you can do so at any time using the button below.
# modules/task.js
testpilot.finishedTask.finishedStudy = Excellent! You finished the "%S" Study!
testpilot.finishedTask.allRelatedDataDeleted = All data related to this study has been removed from your computer.
# modules/setup.js
testpilot.notification.update = Update…
testpilot.notification.thankYouForUploadingData = Thanks!
testpilot.notification.thankYouForUploadingData.message = Thank you for uploading your data.
testpilot.notification.readyToSubmit = Ready to Submit
testpilot.notification.readyToSubmit.message = The Test Pilot "%S" study is finished gathering data and is ready to submit.
testpilot.notification.newTestPilotStudy = New Test Pilot Study
testpilot.notification.newTestPilotStudy.pre.message = The Test Pilot "%S" study is about to begin.
testpilot.notification.newTestPilotSurvey = New Test Pilot Survey
testpilot.notification.newTestPilotSurvey.message = %S
testpilot.notification.newTestPilotResults = New Test Pilot Results
testpilot.notification.newTestPilotResults.message = New results are now available for the Test Pilot "%S" study.
testpilot.notification.autoUploadedData = Thank you!
testpilot.notification.autoUploadedData.message = The Test Pilot "%S" study is completed and your data has been submitted!
testpilot.notification.extensionUpdate = Extension Update
testpilot.notification.extensionUpdate.message = One of your studies requires a newer version of Test Pilot. You can get the latest version using the Add-ons window.

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

@ -15,14 +15,34 @@ EXPORTED_SYMBOLS = ["TestPilotUIBuilder"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const UPDATE_CHANNEL_PREF = "app.update.channel";
const POPUP_SHOW_ON_NEW = "extensions.testpilot.popup.showOnNewStudy";
const POPUP_CHECK_INTERVAL = "extensions.testpilot.popup.delayAfterStartup";
var TestPilotUIBuilder = {
__prefs: null,
get _prefs() {
this.__prefs = Cc["@mozilla.org/preferences-service;1"]
delete this._prefs;
return this._prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
return this.__prefs;
},
get _prefDefaultBranch() {
delete this._prefDefaultBranch;
return this._prefDefaultBranch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService).getDefaultBranch("");
},
get _comparator() {
delete this._comparator;
return this._comparator = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
},
get _appVersion() {
delete this._appVersion;
return this._appVersion = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo).version;
},
buildTestPilotInterface: function(window) {
@ -34,6 +54,12 @@ var TestPilotUIBuilder = {
feedbackButton = palette.getElementsByAttribute("id", "feedback-menu-button").item(0);
}
feedbackButton.parentNode.removeChild(feedbackButton);
/* Default prefs for test pilot version - default to NOT notifying user about new
* studies starting. Note we're setting default values, not current values -- we
* want these to be overridden by any user set values!!*/
this._prefDefaultBranch.setBoolPref(POPUP_SHOW_ON_NEW, false);
this._prefDefaultBranch.setIntPref(POPUP_CHECK_INTERVAL, 180000);
},
buildFeedbackInterface: function(window) {
@ -62,23 +88,24 @@ var TestPilotUIBuilder = {
} catch (e) {
}
}
/* Pref defaults for Feedback version: default to notifying user about new
* studies starting. Note we're setting default values, not current values -- we
* want these to be overridden by any user set values!!*/
this._prefDefaultBranch.setBoolPref(POPUP_SHOW_ON_NEW, true);
this._prefDefaultBranch.setIntPref(POPUP_CHECK_INTERVAL, 600000);
},
isBetaChannel: function() {
channelUsesFeedback: function() {
// Beta and aurora channels use feedback interface; nightly and release channels don't.
let channel = this._prefs.getCharPref(UPDATE_CHANNEL_PREF);
let channel = this._prefDefaultBranch.getCharPref(UPDATE_CHANNEL_PREF);
return (channel == "beta") || (channel == "betatest") || (channel == "aurora");
},
appVersionIsFinal: function() {
// Return true iff app version >= 4.0 AND there is no "beta" or "rc" in version string.
let appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
let version = appInfo.version;
let versionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
if (versionChecker.compare(version, "4.0") >= 0) {
if (version.indexOf("b") == -1 && version.indexOf("rc") == -1) {
if (this._comparator.compare(this._appVersion, "4.0") >= 0) {
if (this._appVersion.indexOf("b") == -1 && this._appVersion.indexOf("rc") == -1) {
return true;
}
}
@ -93,23 +120,14 @@ var TestPilotUIBuilder = {
return;
}
/* Overlay Feedback XUL if we're in the beta update channel, Test Pilot XUL otherwise.
* Once the overlay is complete, call buildFeedbackInterface() or buildTestPilotInterface(). */
let self = this;
if (this.isBetaChannel()) {
window.document.loadOverlay("chrome://testpilot/content/feedback-browser.xul",
{observe: function(subject, topic, data) {
if (topic == "xul-overlay-merged") {
self.buildFeedbackInterface(window);
}
}});
/* Overlay Feedback XUL if we're in the beta update channel, Test Pilot XUL otherwise, and
* call buildFeedbackInterface() or buildTestPilotInterface(). */
if (this.channelUsesFeedback()) {
window.document.loadOverlay("chrome://testpilot/content/feedback-browser.xul", null);
this.buildFeedbackInterface(window);
} else {
window.document.loadOverlay("chrome://testpilot/content/tp-browser.xul",
{observe: function(subject, topic, data) {
if (topic == "xul-overlay-merged") {
self.buildTestPilotInterface(window);
}
}});
window.document.loadOverlay("chrome://testpilot/content/tp-browser.xul", null);
this.buildTestPilotInterface(window);
}
}
};

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

@ -71,7 +71,7 @@ JarStore.prototype = {
let istream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
// open for reading
istream.init(jarFile, 0x01, 0444, 0);
istream.init(jarFile, 0x01, parseInt("0444", 8), 0);
let ch = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
// Use SHA256, it's more secure than MD5:
@ -112,7 +112,7 @@ JarStore.prototype = {
jarFile.create( Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
let stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(jarFile, 0x04 | 0x08 | 0x20, 0600, 0); // readwrite, create, truncate
stream.init(jarFile, 0x04 | 0x08 | 0x20, parseInt("0600", 8), 0); // readwrite, create, truncate
stream.write(rawData, rawData.length);
if (stream instanceof Ci.nsISafeOutputStream) {
stream.finish();

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

@ -0,0 +1,73 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function(global) {
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
var exports = {};
var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
function MozFile(path) {
var file = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return {
get directoryEntries() {
try {
return file.directoryEntries;
} catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
throw new Error("path does not exist: " + file.path);
}
},
__proto__: file
};
}
exports.join = function join(base) {
if (arguments.length < 2)
throw new Error("need at least 2 args");
base = MozFile(base);
for (var i = 1; i < arguments.length; i++)
base.append(arguments[i]);
return base.path;
};
exports.dirname = function dirname(path) {
return MozFile(path).parent.path;
};
exports.list = function list(path) {
var entries = MozFile(path).directoryEntries;
var entryNames = [];
while(entries.hasMoreElements()) {
var entry = entries.getNext();
entry.QueryInterface(Ci.nsIFile);
entryNames.push(entry.leafName);
}
return entryNames;
};
if (global.window) {
// We're being loaded in a chrome window, or a web page with
// UniversalXPConnect privileges.
global.File = exports;
} else if (global.exports) {
// We're being loaded in a SecurableModule.
for (name in exports) {
global.exports[name] = exports[name];
}
} else {
// We're being loaded in a JS module.
global.EXPORTED_SYMBOLS = [];
for (name in exports) {
global.EXPORTED_SYMBOLS.push(name);
global[name] = exports[name];
}
}
})(this);

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

@ -88,7 +88,7 @@
exports.SandboxFactory.prototype = {
createSandbox: function createSandbox(options) {
var principal = resolvePrincipal(options.principal,
var principal = resolvePrincipal('b' in options ? options.b : undefined,
this._defaultPrincipal);
return {

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

@ -0,0 +1,216 @@
/* 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/. */
/* The TestPilotSetup object will choose one of these implementations to instantiate.
* The interface for all NotificationManager implementations is:
showNotification: function(window, features, choices) {},
hideNotification: function(window) {}
*/
EXPORTED_SYMBOLS = ["CustomNotificationManager", "PopupNotificationManager"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
/* CustomNotificationManager: the one where notifications
* come up from the Test Pilot icon in the addon bar. For Firefox 3.6. */
function CustomNotificationManager(anchorId, tailIsUp) {
this._anchorId = anchorId;
this._tailIsUp = tailIsUp;
}
CustomNotificationManager.prototype = {
showNotification: function TP_OldNotfn_showNotification(window, features, choices) {
let doc = window.document;
let popup = doc.getElementById("pilot-notification-popup");
let textLabel = doc.getElementById("pilot-notification-text");
let titleLabel = doc.getElementById("pilot-notification-title");
let icon = doc.getElementById("pilot-notification-icon");
let button = doc.getElementById("pilot-notification-submit");
let closeBtn = doc.getElementById("pilot-notification-close");
let link = doc.getElementById("pilot-notification-link");
let checkbox = doc.getElementById("pilot-notification-always-submit-checkbox");
let self = this;
let buttonChoice = null;
let linkChoice = null;
let checkBoxChoice = null;
if (this._tailIsUp) {
popup.setAttribute("class", "tail-up");
} else {
popup.setAttribute("class", "tail-down");
}
popup.setAttribute("noautohide", !(features.fragile));
if (features.title) {
titleLabel.setAttribute("value", features.title);
}
while (textLabel.lastChild) {
textLabel.removeChild(textLabel.lastChild);
}
if (features.text) {
textLabel.appendChild(doc.createTextNode(features.text));
}
if (features.iconClass) {
// css will set the image url based on the class.
icon.setAttribute("class", features.iconClass);
}
/* Go through the specified choices and figure out which one to turn into a link, which one
* (if any) to turn into a button, and which one (if any) to turn into a check box. */
for (let i = 0; i < choices.length; i++) {
switch(choices[i].customUiType) {
case "button":
buttonChoice = choices[i];
break;
case "link":
linkChoice = choices[i];
break;
case "checkbox":
checkBoxChoice = choices[i];
break;
}
}
// Create check box if specified:
if (checkBoxChoice) {
checkbox.removeAttribute("hidden");
checkbox.setAttribute("label", checkBoxChoice.label);
} else {
checkbox.setAttribute("hidden", true);
}
// Create button if specified:
if (buttonChoice) {
button.setAttribute("label", buttonChoice.label);
button.onclick = function(event) {
if (event.button == 0) {
if (checkbox.checked && checkBoxChoice) {
checkBoxChoice.callback();
}
buttonChoice.callback();
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
}
};
button.removeAttribute("hidden");
} else {
button.setAttribute("hidden", true);
}
// Create the link if specified:
if (linkChoice) {
link.setAttribute("value", linkChoice.label);
link.setAttribute("class", "notification-link");
link.onclick = function(event) {
if (event.button == 0) {
linkChoice.callback();
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
}
};
link.removeAttribute("hidden");
} else {
link.setAttribute("hidden", true);
}
closeBtn.onclick = function() {
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
};
// Show the popup:
popup.hidden = false;
popup.setAttribute("open", "true");
let anchorElement = window.document.getElementById(this._anchorId);
popup.openPopup(anchorElement, "after_end");
},
hideNotification: function TP_OldNotfn_hideNotification(window) {
let popup = window.document.getElementById("pilot-notification-popup");
popup.removeAttribute("open");
popup.hidePopup();
}
};
// For Fx 4.0 + , uses the built-in doorhanger notification system (but with my own anchor icon)
function PopupNotificationManager() {
/* In the future, we may want to anchor these to the Feedback button if present,
* but for now that option is unimplemented. */
this._popupModule = {};
Components.utils.import("resource://gre/modules/PopupNotifications.jsm", this._popupModule);
this._pn = null;
}
PopupNotificationManager.prototype = {
showNotification: function TP_NewNotfn_showNotification(window, features, choices) {
let self = this;
let tabbrowser = window.getBrowser();
let panel = window.document.getElementById("testpilot-notification-popup");
let iconBox = window.document.getElementById("tp-notification-popup-box");
let defaultChoice = null;
let additionalChoices = [];
// hide any existing notification so we don't get a weird stack
this.hideNotification();
// Create notifications object for window
this._pn = new this._popupModule.PopupNotifications(tabbrowser, panel, iconBox);
/* Add hideNotification() calls to the callbacks of each choice -- the client code shouldn't
* have to worry about hiding the notification in its callbacks.*/
for (let i = 0; i < choices.length; i++) {
let choice = choices[i];
let choiceWithHide = {
label: choice.label,
accessKey: choice.accessKey,
callback: function() {
self.hideNotification();
choice.callback();
}};
// Take the first one to be the default choice:
if (i == 0) {
defaultChoice = choiceWithHide;
} else {
additionalChoices.push(choiceWithHide);
}
}
this._notifRef = this._pn.show(tabbrowser.selectedBrowser,
"testpilot",
features.text,
"tp-notification-popup-icon", // All TP notifications use this icon
defaultChoice,
additionalChoices,
{persistWhileVisible: true,
removeOnDismissal: features.fragile,
title: features.title,
iconClass: features.iconClass,
closeButtonFunc: function() {
self.hideNotification();
},
eventCallback: function(stateChange){
/* Note - closeCallback() will be called AFTER the button handler,
* and will be called no matter whether the notification is closed via
* close button or a menu button item.
*/
if (stateChange == "removed" && features.closeCallback) {
features.closeCallback();
}
self._notifRef = null;
}});
// See http://mxr.mozilla.org/mozilla-central/source/toolkit/content/PopupNotifications.jsm
},
hideNotification: function TP_NewNotfn_hideNotification() {
if (this._notifRef && this._pn) {
this._pn.remove(this._notifRef);
this._notifRef = null;
}
}
};

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

@ -3,10 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const BASE_URL_PREF = "extensions.testpilot.indexBaseURL";
const SSL_DOWNLOAD_REQUIRED_PREF = "extensions.testpilot.ssldownloadrequired";
var Cuddlefish = require("cuddlefish");
var resolveUrl = require("url").resolve;
var SecurableModule = require("securable-module");
let JarStore = require("jar-code-store").JarStore;
let prefs = require("preferences-service");
/* Security info should look like this:
* Security Info:
@ -100,11 +103,16 @@ function downloadFile(url, cb, lastModified) {
console.info("Using binary mode to download jar file.");
req.overrideMimeType('text/plain; charset=x-user-defined');
}
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
// check security channel:
if (verifyChannelSecurity(req.channel)) {
// check security channel, unless the user is ignoring that.
let ssldownloadrequired= prefs.get(SSL_DOWNLOAD_REQUIRED_PREF,true);
if (!ssldownloadrequired) {
dump("not requiring ssl download for experiements. use at your own risk!\n");
dump("change this with: " + SSL_DOWNLOAD_REQUIRED_PREF + "\n");
}
if (!ssldownloadrequired | verifyChannelSecurity(req.channel)) {
cb(req.responseText);
} else {
cb(null);
@ -122,7 +130,7 @@ function downloadFile(url, cb, lastModified) {
cb(null);
}
}
}, false);
};
req.send();
}
@ -377,7 +385,7 @@ exports.RemoteExperimentLoader.prototype = {
let foStream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0666", 8), 0);
// write, create, truncate
let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Ci.nsIConverterOutputStream);

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

@ -24,6 +24,8 @@ const UPDATE_CHANNEL_PREF = "app.update.channel";
const LOG_FILE_NAME = "TestPilotErrorLog.log";
const RANDOM_DEPLOY_PREFIX = "extensions.testpilot.deploymentRandomizer";
Cu.import("resource://testpilot/modules/interface.js");
let TestPilotSetup = {
didReminderAfterStartup: false,
startupComplete: false,
@ -151,30 +153,6 @@ let TestPilotSetup = {
return this.__obs;
},
_isBetaChannel: function TPS__isBetaChannel() {
// Beta and aurora channels use feedback interface; nightly and release channels don't.
let channel = this._prefs.getValue(UPDATE_CHANNEL_PREF, "");
return (channel == "beta") || (channel == "betatest") || (channel == "aurora");
},
_setPrefDefaultsForVersion: function TPS__setPrefDefaultsForVersion() {
/* A couple of preferences need different default values depending on
* whether we're in the Firefox 4 beta version or the standalone TP version
*/
let ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
let prefBranch = ps.getDefaultBranch("");
/* note we're setting default values, not current values -- these
* get overridden by any user set values. */
if (this._isBetaChannel()) {
prefBranch.setBoolPref(POPUP_SHOW_ON_NEW, true);
prefBranch.setIntPref(POPUP_CHECK_INTERVAL, 600000);
} else {
prefBranch.setBoolPref(POPUP_SHOW_ON_NEW, false);
prefBranch.setIntPref(POPUP_CHECK_INTERVAL, 180000);
}
},
globalStartup: function TPS__doGlobalSetup() {
// Only ever run this stuff ONCE, on the first window restore.
// Should get called by the Test Pilot component.
@ -182,7 +160,6 @@ let TestPilotSetup = {
logger.trace("TestPilotSetup.globalStartup was called.");
try {
this._setPrefDefaultsForVersion();
if (!this._prefs.getValue(RUN_AT_ALL_PREF, true)) {
logger.trace("Test Pilot globally disabled: Not starting up.");
return;
@ -222,7 +199,7 @@ let TestPilotSetup = {
/* Show first run page (in front window) only the first time after install;
* Don't show first run page in Feedback UI version. */
if ((self._prefs.getValue(VERSION_PREF, "") == "") &&
(!self._interfaceBuilder.channelUsesFeedback())) {
(!TestPilotUIBuilder.channelUsesFeedback())) {
self._prefs.setValue(VERSION_PREF, self.version);
let browser = self._getFrontBrowserWindow().getBrowser();
let url = self._prefs.getValue(FIRST_RUN_PREF, "");
@ -343,16 +320,21 @@ let TestPilotSetup = {
let doc = window.document;
let popup = doc.getElementById("pilot-notification-popup");
let anchor;
if (this._isBetaChannel()) {
/* If we're in the Ffx4Beta version, popups come down from feedback
* button, but if we're in the standalone extension version, they
* come up from status bar icon. */
let anchor, xOffset;
if (TestPilotUIBuilder.channelUsesFeedback()) {
/* If we're in the Ffx4Beta version, popups hang down from the feedback
* button. In the standalone extension version, they hang down from
* a temporary Test Pilot icon that appears in the toolbar. */
anchor = doc.getElementById("feedback-menu-button");
popup.setAttribute("class", "tail-up");
xOffset = 0;
} else {
anchor = doc.getElementById("pilot-notifications-button");
popup.setAttribute("class", "tail-down");
anchor = doc.getElementById("tp-notification-popup-icon");
anchor.hidden = false;
/* By default, right edge of notification will line up with right edge of anchor.
* That looks fine for feedback button, but the test pilot icon is narrower and
* this alignment causes the pointy part of the arrow to miss the icon. Fix this
* by shifting the notification to the right 24 pixels. */
xOffset = 24;
}
let textLabel = doc.getElementById("pilot-notification-text");
let titleLabel = doc.getElementById("pilot-notification-title");
@ -421,7 +403,6 @@ let TestPilotSetup = {
// Create the link if specified:
if (linkText && (linkUrl || task)) {
link.setAttribute("value", linkText);
link.setAttribute("class", "notification-link");
link.onclick = function(event) {
if (event.button == 0) {
if (task) {
@ -444,7 +425,7 @@ let TestPilotSetup = {
// Show the popup:
popup.hidden = false;
popup.setAttribute("open", "true");
popup.openPopup( anchor, "after_end");
popup.openPopup( anchor, "after_end", xOffset, 0);
},
_openChromeless: function TPS__openChromeless(url) {
@ -462,6 +443,12 @@ let TestPilotSetup = {
popup.setAttribute("open", "false");
popup.removeAttribute("tpisextensionupdate");
popup.hidePopup();
if (!TestPilotUIBuilder.channelUsesFeedback()) {
// If we're using the temporary test pilot icon as an anchor, hide it now
let icon = window.document.getElementById("tp-notification-popup-icon");
icon.hidden = true;
}
if (onCloseCallback) {
onCloseCallback();
}
@ -487,7 +474,7 @@ let TestPilotSetup = {
// Highest priority is if there is a finished test (needs a decision)
if (this._prefs.getValue(POPUP_SHOW_ON_FINISH, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_FINISHED) {
if (!this._prefs.getValue(ALWAYS_SUBMIT_DATA, false)) {
@ -512,7 +499,7 @@ let TestPilotSetup = {
// If there's no finished test, next highest priority is new tests that
// are starting...
if (this._prefs.getValue(POPUP_SHOW_ON_NEW, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_PENDING ||
task.status == TaskConstants.STATUS_NEW) {
@ -538,11 +525,12 @@ let TestPilotSetup = {
task, false,
this._stringBundle.formatStringFromName(
"testpilot.notification.newTestPilotSurvey.message",
[task.title], 1),
// in task.js summary falls back to title if undefined or empty, but we double make sure :)
[task.summary || task.title],1),
this._stringBundle.GetStringFromName(
"testpilot.notification.newTestPilotSurvey"),
"new-study", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
this._stringBundle.GetStringFromName("testpilot.takeSurvey"),
task.defaultUrl);
task.changeStatus(TaskConstants.STATUS_IN_PROGRESS, true);
return;
@ -553,7 +541,7 @@ let TestPilotSetup = {
// And finally, new experiment results:
if (this._prefs.getValue(POPUP_SHOW_ON_RESULTS, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.taskType == TaskConstants.TYPE_RESULTS &&
task.status == TaskConstants.STATUS_NEW) {
@ -645,15 +633,17 @@ let TestPilotSetup = {
}
},
_experimentRequirementsAreMet: function TPS__requirementsMet(experiment) {
/* Returns true if we we meet the requirements to run this experiment
* (e.g. meet the minimum Test Pilot version and Firefox version)
* false if not.
* Default is always to run the study - return true UNLESS the study
* specifies a requirement that we don't meet. */
_checkExperimentRequirements: function TPS__requirementsMet(experiment, callback) {
/* Async.
* Calls callback with a true if we we meet the requirements to run this
* experiment (e.g. meet the minimum Test Pilot version and Firefox version)
* calls callback with a false if not.
* All of the requirements that a study can specify - firefox version, test pilot
* version, filter function etc - default to true if not provided. callback(true)
* UNLESS the study specifies a requirement that we don't meet. */
let logger = this._logger;
try {
let minTpVer, minFxVer, expName, runOrNotFunc, randomDeployment;
let minTpVer, minFxVer, expName, filterFunc, randomDeployment;
/* Could be an experiment, which specifies experimentInfo, or survey,
* which specifies surveyInfo. */
let info = experiment.experimentInfo ?
@ -662,12 +652,13 @@ let TestPilotSetup = {
if (!info) {
// If neither one is supplied, study lacks metadata required to run
logger.warn("Study lacks minimum metadata to run.");
return false;
callback(false);
return;
}
minTpVer = info.minTPVersion;
minFxVer = info.minFXVersion;
expName = info.testName;
runOrNotFunc = info.runOrNotFunc;
filterFunc = info.filter;
randomDeployment = info.randomDeployment;
// Minimum test pilot version:
@ -685,44 +676,54 @@ let TestPilotSetup = {
"testpilot.notification.extensionUpdate"),
"update-extension", true, false, "", "", true);
}
return false;
callback(false);
return;
}
// Minimum firefox version:
if (minFxVer && this._isNewerThanFirefox(minFxVer)) {
logger.warn("Not loading " + expName);
logger.warn("Because it requires Firefox version " + minFxVer);
return false;
callback(false);
return;
}
// Random deployment (used to give study to random subsample of users)
if (randomDeployment) {
/* Roll a hundred-sided die. Remember what we roll for later reference. A study
/* Roll a 100*uniform_random. Remember what we roll for later reference or reuse. A study
* using random subsample deployment will provide a range (say, 0 ~ 30) which means
* only users who roll within that range will run the study. */
* only users who roll not outside that range (inclusive) will run the study.
* ie., [0,1.5] -> 1.5% prob
*/
let prefName = RANDOM_DEPLOY_PREFIX + "." + randomDeployment.rolloutCode;
let myRoll = this._prefs.getValue(prefName, null);
if (myRoll == null) {
myRoll = Math.floor(Math.random()*100);
this._prefs.setValue(prefName, myRoll);
myRoll = Math.random()*100;
this._prefs.setValue(prefName, JSON.stringify(myRoll));
} else {
myRoll = Number(myRoll); // cast it
}
if (myRoll < randomDeployment.minRoll) {
return false;
callback(false);
return;
}
if (myRoll > randomDeployment.maxRoll) {
return false;
callback(false);
return;
}
}
/* The all-purpose, arbitrary code "Should this study run?" function - if
* provided, use its return value. */
if (runOrNotFunc) {
return runOrNotFunc();
* provided, use it. (filterFunc must be asynchronous too!)*/
if (filterFunc) {
filterFunc(callback);
return;
}
} catch (e) {
logger.warn("Error in requirements check " + e);
callback(false); // if something went wrong, err on the side of not running the study
}
return true;
callback(true);
},
checkForTasks: function TPS_checkForTasks(callback) {
@ -743,65 +744,71 @@ let TestPilotSetup = {
// downloading new contents or not...
let experiments = self._remoteExperimentLoader.getExperiments();
let numExperimentsProcessed = 0;
let numExperiments = Object.keys(experiments).length;
for (let filename in experiments) {
if (!self._experimentRequirementsAreMet(experiments[filename])) {
continue;
}
try {
// The try-catch ensures that if something goes wrong in loading one
// experiment, the other experiments after that one still get loaded.
logger.trace("Attempting to load experiment " + filename);
let task;
// Could be a survey: check if surveyInfo is exported:
if (experiments[filename].surveyInfo != undefined) {
let sInfo = experiments[filename].surveyInfo;
// If it supplies questions, it's a built-in survey.
// If not, it's a web-based survey.
if (!sInfo.surveyQuestions) {
task = new self._taskModule.TestPilotWebSurvey(sInfo);
} else {
task = new self._taskModule.TestPilotBuiltinSurvey(sInfo);
self._checkExperimentRequirements(experiments[filename], function(requirementsMet) {
if (requirementsMet) {
try {
/* The try-catch ensures that if something goes wrong in loading one
* experiment, the other experiments after that one still get loaded. */
logger.trace("Attempting to load experiment " + filename);
let task;
// Could be a survey: check if surveyInfo is exported:
if (experiments[filename].surveyInfo != undefined) {
let sInfo = experiments[filename].surveyInfo;
// If it supplies questions, it's a built-in survey.
// If not, it's a web-based survey.
if (!sInfo.surveyQuestions) {
task = new self._taskModule.TestPilotWebSurvey(sInfo);
} else {
task = new self._taskModule.TestPilotBuiltinSurvey(sInfo);
}
} else {
// This one must be an experiment.
let expInfo = experiments[filename].experimentInfo;
let dsInfo = experiments[filename].dataStoreInfo;
let dataStore = new self._dataStoreModule.ExperimentDataStore(
dsInfo.fileName, dsInfo.tableName, dsInfo.columns );
let webContent = experiments[filename].webContent;
task = new self._taskModule.TestPilotExperiment(expInfo,
dataStore,
experiments[filename].handlers,
webContent);
}
self.addTask(task);
logger.info("Loaded task " + filename);
} catch (e) {
logger.warn("Failed to load task " + filename + ": " + e);
}
} else {
// This one must be an experiment.
let expInfo = experiments[filename].experimentInfo;
let dsInfo = experiments[filename].dataStoreInfo;
let dataStore = new self._dataStoreModule.ExperimentDataStore(
dsInfo.fileName, dsInfo.tableName, dsInfo.columns );
let webContent = experiments[filename].webContent;
task = new self._taskModule.TestPilotExperiment(expInfo,
dataStore,
experiments[filename].handlers,
webContent);
}
self.addTask(task);
logger.info("Loaded task " + filename);
} catch (e) {
logger.warn("Failed to load task " + filename + ": " + e);
}
} // end if requirements met
// whether loading succeeded or failed, we're done processing that one; increment the count:
numExperimentsProcessed ++;
if (numExperimentsProcessed == numExperiments) {
// all done with experiments -- do results and legacy studies:
let results = self._remoteExperimentLoader.getStudyResults();
for (let r in results) {
let studyResult = new self._taskModule.TestPilotStudyResults(results[r]);
self.addTask(studyResult);
}
/* Legacy studies = stuff we no longer have the code for, but
* if the user participated in it we want to keep that metadata. */
let legacyStudies = self._remoteExperimentLoader.getLegacyStudies();
for (let l in legacyStudies) {
let legacyStudy = new self._taskModule.TestPilotLegacyStudy(legacyStudies[l]);
self.addTask(legacyStudy);
}
// Finally, call the callback if there is one
if (callback) {
callback();
}
} // end of if all experiments are processed
}); // end of call to check experiment requirements
} // end for filename in experiments
// Handling new results is much simpler:
let results = self._remoteExperimentLoader.getStudyResults();
for (let r in results) {
let studyResult = new self._taskModule.TestPilotStudyResults(results[r]);
self.addTask(studyResult);
}
/* Legacy studies = stuff we no longer have the code for, but
* if the user participated in it we want to keep that metadata. */
let legacyStudies = self._remoteExperimentLoader.getLegacyStudies();
for (let l in legacyStudies) {
let legacyStudy = new self._taskModule.TestPilotLegacyStudy(legacyStudies[l]);
self.addTask(legacyStudy);
}
if (callback) {
callback();
}
}
);
); // end of call to checkForUpdates
},
reloadRemoteExperiments: function TPS_reloadRemoteExperiments(callback) {

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

@ -813,7 +813,7 @@ TestPilotExperiment.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", dataString.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 || req.status == 202) {
let location = req.getResponseHeader("Location");
@ -854,7 +854,7 @@ TestPilotExperiment.prototype = {
callback(false);
}
}
}, false);
};
req.send(dataString);
});
},
@ -886,7 +886,7 @@ TestPilotExperiment.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", dataString.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 || req.status == 202) {
logger.info("Quit reason posted successfully " + req.responseText);
@ -900,7 +900,7 @@ TestPilotExperiment.prototype = {
}
}
}
}, false);
};
logger.trace("Sending quit reason.");
req.send(dataString);
} else {
@ -1036,7 +1036,7 @@ TestPilotBuiltinSurvey.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", params.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 ||
req.status == 202) {
@ -1066,7 +1066,7 @@ TestPilotBuiltinSurvey.prototype = {
callback(false);
}
}
}, false);
};
req.send(params);
});
}

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

@ -3,43 +3,43 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
padding-bottom: 40px;
padding-bottom: 40px;
padding-top: 20px;
padding-left: 0px;
padding-right: 0px;
}
body {
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Lucida, Arial, Helvetica, sans-serif;
background: #fff url('chrome://testpilot/skin/images/bg-status.jpg') repeat-x top center; padding: 0px;
background: #fff url('chrome://testpilot/skin/images/bg-status.jpg') repeat-x top center; padding: 0px;
color:#787878;
font-size:12px;
line-height: 18px;
margin: 0 auto;
}
h1 {color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {color: #54717b; font-size: 24px; line-height: 24px;font-weight: normal; letter-spacing: -1px}
h3 {color: #54717b; font-size: 18px; line-height: 18px;font-weight: normal; letter-spacing: -1px}
p, ul {font-size: 12px;}
a:link, a:hover, a:active, a:focus, a:visited {color: #9f423b; text-decoration: none;}
a:hover {color: #9f423b; text-decoration: none;}
.bold {color:#3a3a3a;}
.address {margin-left: 20px;}
.inactive {color:#ccc;}
li {list-style-type: circle;}
.spacer {height: 40px;}
.center {text-align: center;}
.data {
background-color: #fff;
margin-bottom: 6px;
border: 1px solid rgba(133, 153, 166, 0.2);
@ -48,11 +48,9 @@ body {
padding: 6px;
box-shadow:
rgba(133, 153, 166, 0.4) 0px 1px 17px;
-webkit-box-shadow:
rgba(133, 153, 166, 0.4) 0px 1px 24px;
}
.dataBox {
font-size: 16px;
padding: 6px 20px 20px 20px;
@ -89,16 +87,6 @@ body {
margin-top: 240px;
}
.downloadButton {
margin-bottom: -10px;
margin-top: -2px;
margin-right: 12px;
}
.downloadH1 {color: #3a3a3a; font-size: 24px; font-weight: normal; letter-spacing: -1px; margin-right: 8px;}
.downloadH2 {color: #3a3a3a; font-size: 22px; font-weight: normal; letter-spacing: -1px; line-height: 28px; margin-left: 46px;}
#intro {
float: left;
width: 500px;
@ -117,12 +105,12 @@ body {
padding: 8px 12px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 8.5px;
background-color: #e7eaec;
@ -135,12 +123,12 @@ body {
width: 240px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 8.5px;
background-color: #e7eaec;
@ -154,7 +142,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -175,7 +163,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -191,7 +179,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout_continue.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -227,7 +215,7 @@ body {
margin-right: 6px;
}
/* ------- MENU -------
/* ------- MENU -------
#menu {
margin: 20px auto;
@ -242,7 +230,7 @@ body {
border-bottom: 3px solid #adb6ba;
-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
background-color: #fff;
}
}
*/
@ -260,7 +248,7 @@ body {
font-size: 14px;
text-shadow: 1px 1px 1px rgba(173, 182, 186, 1);
background-color: rgba(173, 182, 186, 0.3);
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 -10px 8.5px;
padding: 9px 8px 8px 8px;
}

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

@ -4,6 +4,7 @@
#pilot-notification-popup {
-moz-appearance: none;
border-image: none;
background-color: Menu;
background-image: -moz-linear-gradient(hsla(0,0%,100%,.2), transparent);
box-shadow: inset 0 0 7px hsla(0,0%,100%,.2),
@ -14,11 +15,6 @@
width: 480px;
}
.tail-up,
.tail-down {
-moz-border-image: none;
}
.pilot-notification-popup-container {
-moz-appearance: none;
margin: 0;

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

@ -6,7 +6,7 @@
#pilot-notification-submit {
-moz-appearance: none;
background: #666
background: #666
-moz-linear-gradient(rgba(110,110,110,.9), rgba(70,70,70,.9) 49%,
rgba(60,60,60,.9) 51%, rgba(50,50,50,.9));
background-clip: padding-box;
@ -33,6 +33,6 @@
color: #fff;
}
.notification-link {
#pilot-notification-link {
color: #fff;
}

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

@ -407,7 +407,7 @@ function clearAllPrefsForStudy(studyId) {
dump("Looking for prefs to delete...\n");
let prefService = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch);
.QueryInterface(Ci.nsIPrefBranch2);
let prefStem = "extensions.testpilot";
let prefNames = prefService.getChildList(prefStem);
for each (let prefName in prefNames) {
@ -783,4 +783,4 @@ function testSameGUIDs() {
}
// TODO test for continuity of GUID with recurring study (longitudinal) - i don't think this
// has actually been working so far because a new GUID is generted every time the study starts up...
// has actually been working so far because a new GUID is generted every time the study starts up...

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

@ -1010,7 +1010,7 @@ pref("devtools.inspector.htmlHeight", 112);
pref("devtools.inspector.htmlPanelOpen", false);
pref("devtools.inspector.sidebarOpen", false);
pref("devtools.inspector.activeSidebar", "ruleview");
pref("devtools.inspector.markupPreview", true);
pref("devtools.inspector.markupPreview", false);
// Enable the Layout View
pref("devtools.layoutview.enabled", true);

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

@ -158,26 +158,7 @@ const gXPInstallObserver = {
}
else {
messageString = gNavigatorBundle.getString("addonsInstalled");
action = {
label: gNavigatorBundle.getString("addonInstallManage"),
accessKey: gNavigatorBundle.getString("addonInstallManage.accesskey"),
callback: function() {
// Calculate the add-on type that is most popular in the list of
// installs
var types = {};
var bestType = null;
for (let install of installInfo.installs) {
if (install.type in types)
types[install.type]++;
else
types[install.type] = 1;
if (!bestType || types[install.type] > types[bestType])
bestType = install.type;
}
BrowserOpenAddonsMgr("addons://list/" + bestType);
}
};
action = null;
}
messageString = PluralForm.get(installInfo.installs.length, messageString);

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

@ -484,7 +484,7 @@ var gPluginHandler = {
let messageString = gNavigatorBundle.getString("activatePluginsMessage.message");
let mainAction = {
label: gNavigatorBundle.getString("activatePluginsMessage.label"),
label: gNavigatorBundle.getString("activateAllPluginsMessage.label"),
accessKey: gNavigatorBundle.getString("activatePluginsMessage.accesskey"),
callback: function() { gPluginHandler.activatePlugins(contentWindow); }
};

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

@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// The minimum sizes for the auto-resize panel code.
const PANEL_MIN_HEIGHT = 300;
const PANEL_MIN_HEIGHT = 100;
const PANEL_MIN_WIDTH = 330;
let SocialUI = {
@ -203,7 +203,7 @@ let SocialChatBar = {
}
}
function sizeSocialPanelToContent(iframe) {
function sizeSocialPanelToContent(panel, iframe) {
// FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
let doc = iframe.contentDocument;
if (!doc || !doc.body) {
@ -214,9 +214,17 @@ function sizeSocialPanelToContent(iframe) {
let cs = doc.defaultView.getComputedStyle(body);
let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
let height = Math.max(computedHeight, PANEL_MIN_HEIGHT);
iframe.style.height = height + "px";
let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
let width = Math.max(computedWidth, PANEL_MIN_WIDTH);
let wDiff = width - iframe.getBoundingClientRect().width;
// A panel resize will move the right margin - if that is where the anchor
// arrow is, the arrow will be mis-aligned from the anchor. So we move the
// popup to compensate for that. See bug 799014.
if (wDiff !== 0 && panel.getAttribute("side") == "right") {
let box = panel.boxObject;
panel.moveTo(box.screenX - wDiff, box.screenY);
}
iframe.style.height = height + "px";
iframe.style.width = width + "px";
}
@ -225,18 +233,18 @@ function DynamicResizeWatcher() {
}
DynamicResizeWatcher.prototype = {
start: function DynamicResizeWatcher_start(iframe) {
start: function DynamicResizeWatcher_start(panel, iframe) {
this.stop(); // just in case...
let doc = iframe.contentDocument;
this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) {
sizeSocialPanelToContent(iframe);
sizeSocialPanelToContent(panel, iframe);
});
// Observe anything that causes the size to change.
let config = {attributes: true, characterData: true, childList: true, subtree: true};
this._mutationObserver.observe(doc, config);
// and since this may be setup after the load event has fired we do an
// initial resize now.
sizeSocialPanelToContent(iframe);
sizeSocialPanelToContent(panel, iframe);
},
stop: function DynamicResizeWatcher_stop() {
if (this._mutationObserver) {
@ -309,19 +317,20 @@ let SocialFlyout = {
},
onShown: function(aEvent) {
let iframe = this.panel.firstChild;
let panel = this.panel;
let iframe = panel.firstChild;
this._dynamicResizer = new DynamicResizeWatcher();
iframe.docShell.isActive = true;
iframe.docShell.isAppTab = true;
if (iframe.contentDocument.readyState == "complete") {
this._dynamicResizer.start(iframe);
this._dynamicResizer.start(panel, iframe);
this.dispatchPanelEvent("socialFrameShow");
} else {
// first time load, wait for load and dispatch after load
iframe.addEventListener("load", function panelBrowserOnload(e) {
iframe.removeEventListener("load", panelBrowserOnload, true);
setTimeout(function() {
SocialFlyout._dynamicResizer.start(iframe);
SocialFlyout._dynamicResizer.start(panel, iframe);
SocialFlyout.dispatchPanelEvent("socialFrameShow");
}, 0);
}, true);
@ -366,7 +375,7 @@ let SocialFlyout = {
}
}
sizeSocialPanelToContent(iframe);
sizeSocialPanelToContent(panel, iframe);
let anchor = document.getElementById("social-sidebar-browser");
if (panel.state == "open") {
// this is painful - there is no way to say "move to a new anchor offset",
@ -624,21 +633,57 @@ var SocialToolbar = {
updateButton: function SocialToolbar_updateButton() {
this.updateButtonHiddenState();
let provider = Social.provider;
let iconNames = Object.keys(provider.ambientNotificationIcons);
let icons = provider.ambientNotificationIcons;
let iconNames = Object.keys(icons);
let iconBox = document.getElementById("social-toolbar-item");
let notifBox = document.getElementById("social-notification-box");
let panel = document.getElementById("social-notification-panel");
panel.hidden = false;
let command = document.getElementById("Social:ToggleNotifications");
command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
const CACHE_PREF_NAME = "social.cached.notificationIcons";
// provider.profile == undefined means no response yet from the provider
// to tell us whether the user is logged in or not.
if (!SocialUI.haveLoggedInUser() && provider.profile !== undefined) {
// The provider has responded with a profile and the user isn't logged
// in. The icons etc have already been removed by
// updateButtonHiddenState, so we want to nuke any cached icons we
// have and get out of here!
Services.prefs.clearUserPref(CACHE_PREF_NAME);
return;
}
if (Social.provider.profile === undefined) {
// provider has not told us about the login state yet - see if we have
// a cached version for this provider.
let cached;
try {
cached = JSON.parse(Services.prefs.getCharPref(CACHE_PREF_NAME));
} catch (ex) {}
if (cached && cached.provider == Social.provider.origin && cached.data) {
icons = cached.data;
iconNames = Object.keys(icons);
// delete the counter data as it is almost certainly stale.
for each(let name in iconNames) {
icons[name].counter = '';
}
}
} else {
// We have a logged in user - save the current set of icons back to the
// "cache" so we can use them next startup.
Services.prefs.setCharPref(CACHE_PREF_NAME,
JSON.stringify({provider: Social.provider.origin,
data: icons}));
}
let notificationFrames = document.createDocumentFragment();
let iconContainers = document.createDocumentFragment();
let createdFrames = [];
let command = document.getElementById("Social:ToggleNotifications");
command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
for each(let name in iconNames) {
let icon = provider.ambientNotificationIcons[name];
let icon = icons[name];
let notificationFrameId = "social-status-" + icon.name;
let notificationFrame = document.getElementById(notificationFrameId);
@ -756,13 +801,13 @@ var SocialToolbar = {
notificationFrame.docShell.isActive = true;
notificationFrame.docShell.isAppTab = true;
if (notificationFrame.contentDocument.readyState == "complete") {
dynamicResizer.start(notificationFrame);
dynamicResizer.start(panel, notificationFrame);
dispatchPanelEvent("socialFrameShow");
} else {
// first time load, wait for load and dispatch after load
notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
notificationFrame.removeEventListener("load", panelBrowserOnload, true);
dynamicResizer.start(notificationFrame);
dynamicResizer.start(panel, notificationFrame);
setTimeout(function() {
dispatchPanelEvent("socialFrameShow");
}, 0);

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

@ -4158,7 +4158,7 @@ var XULBrowserWindow = {
// Don't need to re-enable/disable find commands for same-document location changes
// (e.g. the replaceStates in about:addons)
if (!(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
if (content.document.readyState == 'interactive' || content.document.readyState == "complete")
if (content.document.readyState == "interactive" || content.document.readyState == "complete")
disableFindCommands(shouldDisableFind(content.document));
else {
content.document.addEventListener("readystatechange", onContentRSChange);

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

@ -336,7 +336,6 @@ function test_restartless() {
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Open Add-ons Manager", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test has been installed successfully.",
"Should have seen the right message");

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

@ -1 +1 @@
18.0a1
19.0a1

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

@ -28,79 +28,82 @@ gcli.addCommand({
description: gcli.lookup('jsbUrlDesc')
},
{
name: 'indentSize',
type: 'number',
description: gcli.lookup('jsbIndentSizeDesc'),
manual: gcli.lookup('jsbIndentSizeManual'),
defaultValue: 2
},
{
name: 'indentChar',
type: {
name: 'selection',
lookup: [
{ name: "space", value: " " },
{ name: "tab", value: "\t" }
]
},
description: gcli.lookup('jsbIndentCharDesc'),
manual: gcli.lookup('jsbIndentCharManual'),
defaultValue: ' ',
},
{
name: 'preserveNewlines',
type: 'boolean',
description: gcli.lookup('jsbPreserveNewlinesDesc'),
manual: gcli.lookup('jsbPreserveNewlinesManual')
},
{
name: 'preserveMaxNewlines',
type: 'number',
description: gcli.lookup('jsbPreserveMaxNewlinesDesc'),
manual: gcli.lookup('jsbPreserveMaxNewlinesManual'),
defaultValue: -1
},
{
name: 'jslintHappy',
type: 'boolean',
description: gcli.lookup('jsbJslintHappyDesc'),
manual: gcli.lookup('jsbJslintHappyManual')
},
{
name: 'braceStyle',
type: {
name: 'selection',
data: ['collapse', 'expand', 'end-expand', 'expand-strict']
},
description: gcli.lookup('jsbBraceStyleDesc'),
manual: gcli.lookup('jsbBraceStyleManual'),
defaultValue: "collapse"
},
{
name: 'spaceBeforeConditional',
type: 'boolean',
description: gcli.lookup('jsbSpaceBeforeConditionalDesc'),
manual: gcli.lookup('jsbSpaceBeforeConditionalManual')
},
{
name: 'unescapeStrings',
type: 'boolean',
description: gcli.lookup('jsbUnescapeStringsDesc'),
manual: gcli.lookup('jsbUnescapeStringsManual')
group: gcli.lookup("jsbOptionsDesc"),
params: [
{
name: 'indentSize',
type: 'number',
description: gcli.lookup('jsbIndentSizeDesc'),
manual: gcli.lookup('jsbIndentSizeManual'),
defaultValue: 2
},
{
name: 'indentChar',
type: {
name: 'selection',
lookup: [
{ name: "space", value: " " },
{ name: "tab", value: "\t" }
]
},
description: gcli.lookup('jsbIndentCharDesc'),
manual: gcli.lookup('jsbIndentCharManual'),
defaultValue: ' ',
},
{
name: 'doNotPreserveNewlines',
type: 'boolean',
description: gcli.lookup('jsbDoNotPreserveNewlinesDesc')
},
{
name: 'preserveMaxNewlines',
type: 'number',
description: gcli.lookup('jsbPreserveMaxNewlinesDesc'),
manual: gcli.lookup('jsbPreserveMaxNewlinesManual'),
defaultValue: -1
},
{
name: 'jslintHappy',
type: 'boolean',
description: gcli.lookup('jsbJslintHappyDesc'),
manual: gcli.lookup('jsbJslintHappyManual')
},
{
name: 'braceStyle',
type: {
name: 'selection',
data: ['collapse', 'expand', 'end-expand', 'expand-strict']
},
description: gcli.lookup('jsbBraceStyleDesc'),
manual: gcli.lookup('jsbBraceStyleManual'),
defaultValue: "collapse"
},
{
name: 'noSpaceBeforeConditional',
type: 'boolean',
description: gcli.lookup('jsbNoSpaceBeforeConditionalDesc')
},
{
name: 'unescapeStrings',
type: 'boolean',
description: gcli.lookup('jsbUnescapeStringsDesc'),
manual: gcli.lookup('jsbUnescapeStringsManual')
}
]
}
],
exec: function(args, context) {
let opts = {
indent_size: args.indentSize,
indent_char: args.indentChar,
preserve_newlines: args.preserveNewlines,
preserve_newlines: !args.doNotPreserveNewlines,
max_preserve_newlines: args.preserveMaxNewlines == -1 ?
undefined : args.preserveMaxNewlines,
jslint_happy: args.jslintHappy,
brace_style: args.braceStyle,
space_before_conditional: args.spaceBeforeConditional,
space_before_conditional: !args.noSpaceBeforeConditional,
unescape_strings: args.unescapeStrings
}
};
let xhr = new XMLHttpRequest();
@ -117,13 +120,13 @@ gcli.addCommand({
if (xhr.status == 200 || xhr.status == 0) {
let browserDoc = context.environment.chromeDocument;
let browserWindow = browserDoc.defaultView;
let browser = browserWindow.gBrowser;
browser.selectedTab = browser.addTab("data:text/plain;base64," +
browserWindow.btoa(js_beautify(xhr.responseText, opts)));
let gBrowser = browserWindow.gBrowser;
let result = js_beautify(xhr.responseText, opts);
browserWindow.Scratchpad.ScratchpadManager.openScratchpad({text: result});
promise.resolve();
}
else {
} else {
promise.resolve("Unable to load page to beautify: " + args.url + " " +
xhr.status + " " + xhr.statusText);
}

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

@ -34,3 +34,6 @@
direction: ltr;
}
.gcli-row-out .nowrap {
white-space: nowrap;
}

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

@ -3056,7 +3056,10 @@ exports.setContents = function(elem, contents) {
return;
}
if (exports.isXmlDocument(elem.ownerDocument)) {
if ('innerHTML' in elem) {
elem.innerHTML = contents;
}
else {
try {
var ns = elem.ownerDocument.documentElement.namespaceURI;
if (!ns) {
@ -3076,9 +3079,6 @@ exports.setContents = function(elem, contents) {
throw ex;
}
}
else {
elem.innerHTML = contents;
}
};
/**
@ -5469,7 +5469,7 @@ function Requisition(environment, doc) {
// The command that we are about to execute.
// @see setCommandConversion()
this.commandAssignment = new CommandAssignment();
this._setAssignment(this.commandAssignment, null, true);
this.setAssignment(this.commandAssignment, null);
// The object that stores of Assignment objects that we are filling out.
// The Assignment objects are stored under their param.name for named
@ -5556,7 +5556,7 @@ Requisition.prototype._commandAssignmentChanged = function(ev) {
for (var i = 0; i < command.params.length; i++) {
var param = command.params[i];
var assignment = new Assignment(param, i);
this._setAssignment(assignment, null, true);
this.setAssignment(assignment, null);
assignment.onAssignmentChange.add(this._assignmentChanged, this);
this._assignments[param.name] = assignment;
}
@ -5678,27 +5678,23 @@ Requisition.prototype.getAssignments = function(includeCommand) {
return assignments;
};
/**
* Alter the given assignment using the given arg.
* @param assignment The assignment to alter
* @param arg The new value for the assignment. An instance of Argument, or an
* instance of Conversion, or null to set the blank value.
*/
Requisition.prototype.setAssignment = function(assignment, arg) {
this._setAssignment(assignment, arg, false);
};
/**
* Internal function to alter the given assignment using the given arg.
* @param assignment The assignment to alter
* @param arg The new value for the assignment. An instance of Argument, or an
* instance of Conversion, or null to set the blank value.
* @param skipArgUpdate (default=false) Adjusts the args in this requisition to
* keep things up to date. Args should only be skipped when setAssignment is
* being called as part of the update process.
* @param options There are a number of ways to customize how the assignment
* is made, including:
* - argUpdate: (default:false) Adjusts the args in this requisition to keep
* things up to date. Args should only be skipped when setAssignment is being
* called as part of the update process.
* - matchPadding: (default:false) If argUpdate=true, and matchPadding=true
* then further take the step of altering the whitespace on the prefix and
* suffix of the new argument to match that of the old argument.
*/
Requisition.prototype._setAssignment = function(assignment, arg, skipArgUpdate) {
if (!skipArgUpdate) {
Requisition.prototype.setAssignment = function(assignment, arg, options) {
options = options || {};
if (options.argUpdate) {
var originalArgs = assignment.arg.getArgs();
// Update the args array
@ -5724,6 +5720,16 @@ Requisition.prototype._setAssignment = function(assignment, arg, skipArgUpdate)
this._args.splice(index, 1);
}
else {
if (options.matchPadding) {
if (replacementArgs[i].prefix.length === 0 &&
this._args[index].prefix.length !== 0) {
replacementArgs[i].prefix = this._args[index].prefix;
}
if (replacementArgs[i].suffix.length === 0 &&
this._args[index].suffix.length !== 0) {
replacementArgs[i].suffix = this._args[index].suffix;
}
}
this._args[index] = replacementArgs[i];
}
}
@ -5761,7 +5767,7 @@ Requisition.prototype._setAssignment = function(assignment, arg, skipArgUpdate)
*/
Requisition.prototype.setBlankArguments = function() {
this.getAssignments().forEach(function(assignment) {
this._setAssignment(assignment, null, true);
this.setAssignment(assignment, null);
}, this);
};
@ -5801,13 +5807,13 @@ Requisition.prototype.complete = function(cursor, predictionChoice) {
// logic, so we don't use addSpace
if (assignment.isInName()) {
var newArg = assignment.conversion.arg.beget({ prefixPostSpace: true });
this.setAssignment(assignment, newArg);
this.setAssignment(assignment, newArg, { argUpdate: true });
}
}
else {
// Mutate this argument to hold the completion
var arg = assignment.arg.beget({ text: prediction.name });
this.setAssignment(assignment, arg);
this.setAssignment(assignment, arg, { argUpdate: true });
if (!prediction.incomplete) {
// The prediction is complete, add a space to let the user move-on
@ -5832,7 +5838,7 @@ Requisition.prototype.complete = function(cursor, predictionChoice) {
Requisition.prototype._addSpace = function(assignment) {
var arg = assignment.conversion.arg.beget({ suffixSpace: true });
if (arg !== assignment.conversion.arg) {
this.setAssignment(assignment, arg);
this.setAssignment(assignment, arg, { argUpdate: true });
}
};
@ -5844,7 +5850,7 @@ Requisition.prototype.decrement = function(assignment) {
if (replacement != null) {
var str = assignment.param.type.stringify(replacement);
var arg = assignment.conversion.arg.beget({ text: str });
this.setAssignment(assignment, arg);
this.setAssignment(assignment, arg, { argUpdate: true });
}
};
@ -5856,7 +5862,7 @@ Requisition.prototype.increment = function(assignment) {
if (replacement != null) {
var str = assignment.param.type.stringify(replacement);
var arg = assignment.conversion.arg.beget({ text: str });
this.setAssignment(assignment, arg);
this.setAssignment(assignment, arg, { argUpdate: true });
}
};
@ -6514,7 +6520,7 @@ Requisition.prototype._split = function(args) {
// Special case: if the user enters { console.log('foo'); } then we need to
// use the hidden 'eval' command
conversion = new Conversion(evalCommand, new ScriptArgument());
this._setAssignment(this.commandAssignment, conversion, true);
this.setAssignment(this.commandAssignment, conversion);
return;
}
@ -6541,7 +6547,7 @@ Requisition.prototype._split = function(args) {
argsUsed++;
}
this._setAssignment(this.commandAssignment, conversion, true);
this.setAssignment(this.commandAssignment, conversion);
for (var i = 0; i < argsUsed; i++) {
args.shift();
@ -6588,7 +6594,7 @@ Requisition.prototype._assign = function(args) {
var assignment = this.getAssignment(0);
if (assignment.param.type instanceof StringType) {
var arg = (args.length === 1) ? args[0] : new MergedArgument(args);
this._setAssignment(assignment, arg, true);
this.setAssignment(assignment, arg);
return;
}
}
@ -6633,7 +6639,7 @@ Requisition.prototype._assign = function(args) {
arrayArg.addArgument(arg);
}
else {
this._setAssignment(assignment, arg, true);
this.setAssignment(assignment, arg);
}
}
else {
@ -6650,7 +6656,7 @@ Requisition.prototype._assign = function(args) {
// If not set positionally, and we can't set it non-positionally,
// we have to default it to prevent previous values surviving
if (!assignment.param.isPositionalAllowed) {
this._setAssignment(assignment, null, true);
this.setAssignment(assignment, null);
return;
}
@ -6667,7 +6673,7 @@ Requisition.prototype._assign = function(args) {
}
else {
if (args.length === 0) {
this._setAssignment(assignment, null, true);
this.setAssignment(assignment, null);
}
else {
var arg = args.splice(0, 1)[0];
@ -6681,7 +6687,7 @@ Requisition.prototype._assign = function(args) {
this._unassigned.push(new UnassignedAssignment(this, arg));
}
else {
this._setAssignment(assignment, arg, true);
this.setAssignment(assignment, arg);
}
}
}
@ -6690,7 +6696,7 @@ Requisition.prototype._assign = function(args) {
// Now we need to assign the array argument (if any)
Object.keys(arrayArgs).forEach(function(name) {
var assignment = this.getAssignment(name);
this._setAssignment(assignment, arrayArgs[name], true);
this.setAssignment(assignment, arrayArgs[name]);
}, this);
// What's left is can't be assigned, but we need to extract
@ -8066,8 +8072,10 @@ JavascriptField.prototype.setConversion = function(conversion) {
};
JavascriptField.prototype.itemClicked = function(ev) {
this.onFieldChange(ev);
this.setMessage(ev.conversion.message);
var conversion = this.type.parse(ev.arg);
this.onFieldChange({ conversion: conversion });
this.setMessage(conversion.message);
};
JavascriptField.prototype.onInputChange = function(ev) {
@ -8180,12 +8188,11 @@ Menu.prototype.destroy = function() {
* @param ev The click event from the browser
*/
Menu.prototype.onItemClickInternal = function(ev) {
var name = ev.currentTarget.querySelector('.gcli-menu-name').innerHTML;
var name = ev.currentTarget.querySelector('.gcli-menu-name').textContent;
var arg = new Argument(name);
arg.suffix = ' ';
var conversion = this.type.parse(arg);
this.onItemClick({ conversion: conversion });
this.onItemClick({ arg: arg });
};
/**
@ -8201,7 +8208,7 @@ Menu.prototype.show = function(items, match) {
if (match) {
this.items = this.items.map(function(item) {
return gethighlightingProxy(item, match, this.template.ownerDocument);
return getHighlightingProxy(item, match, this.template.ownerDocument);
}.bind(this));
}
@ -8227,7 +8234,7 @@ Menu.prototype.show = function(items, match) {
/**
* Create a proxy around an item that highlights matching text
*/
function gethighlightingProxy(item, match, document) {
function getHighlightingProxy(item, match, document) {
if (typeof Proxy === 'undefined') {
return item;
}
@ -8289,12 +8296,12 @@ Menu.prototype.selectChoice = function() {
return false;
}
var name = selected.innerHTML;
var name = selected.textContent;
var arg = new Argument(name);
arg.suffix = ' ';
arg.prefix = ' ';
var conversion = this.type.parse(arg);
this.onItemClick({ conversion: conversion });
this.onItemClick({ arg: arg });
return true;
};
@ -8498,8 +8505,10 @@ SelectionTooltipField.prototype.setConversion = function(conversion) {
};
SelectionTooltipField.prototype.itemClicked = function(ev) {
this.onFieldChange(ev);
this.setMessage(ev.conversion.message);
var conversion = this.type.parse(ev.arg);
this.onFieldChange({ conversion: conversion });
this.setMessage(conversion.message);
};
SelectionTooltipField.prototype.onInputChange = function(ev) {
@ -10340,7 +10349,8 @@ Tooltip.prototype.selectChoice = function(ev) {
* Called by the onFieldChange event on the current Field
*/
Tooltip.prototype.fieldChanged = function(ev) {
this.requisition.setAssignment(this.assignment, ev.conversion.arg);
var options = { argUpdate: true, matchPadding: true };
this.requisition.setAssignment(this.assignment, ev.conversion.arg, options);
var isError = ev.conversion.message != null && ev.conversion.message !== '';
this.focusManager.setError(isError);

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

@ -6,52 +6,68 @@
const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
"test/browser_cmd_jsb_script.jsi";
let scratchpadWin = null;
let Scratchpad = null;
function test() {
DeveloperToolbarTest.test("about:blank", [ /*GJT_test*/ ]);
DeveloperToolbarTest.test("about:blank", [ GJT_test ]);
}
function GJT_test() {
helpers.setInput('jsb');
helpers.check({
input: 'jsb',
hints: ' <url> [indentSize] [indentChar] [preserveNewlines] [preserveMaxNewlines] [jslintHappy] [braceStyle] [spaceBeforeConditional] [unescapeStrings]',
hints: ' <url> [options]',
markup: 'VVV',
status: 'ERROR'
});
DeveloperToolbarTest.exec({ completed: false });
gBrowser.addTabsProgressListener({
onProgressChange: DeveloperToolbarTest.checkCalled(function GJT_onProgressChange(aBrowser) {
gBrowser.removeTabsProgressListener(this);
Services.ww.registerNotification(function(aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
Services.ww.unregisterNotification(arguments.callee);
let win = aBrowser._contentWindow;
let uri = win.document.location.href;
let result = win.atob(uri.replace(/.*,/, ""));
scratchpadWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
scratchpadWin.addEventListener("load", function GDT_onLoad() {
scratchpadWin.removeEventListener("load", GDT_onLoad, false);
Scratchpad = scratchpadWin.Scratchpad;
result = result.replace(/[\r\n]]/g, "\n");
let observer = {
onReady: function GJT_onReady() {
Scratchpad.removeObserver(observer);
let correct = "function somefunc() {\n" +
" for (let n = 0; n < 500; n++) {\n" +
" if (n % 2 == 1) {\n" +
" console.log(n);\n" +
" console.log(n + 1);\n" +
" }\n" +
" }\n" +
"}";
is(result, correct, "JS has been correctly prettified");
})
});
let result = Scratchpad.getText();
result = result.replace(/[\r\n]]*/g, "\n");
let correct = "function somefunc() {\n" +
" if (true) // Some comment\n" +
" doSomething();\n" +
" for (let n = 0; n < 500; n++) {\n" +
" if (n % 2 == 1) {\n" +
" console.log(n);\n" +
" console.log(n + 1);\n" +
" }\n" +
" }\n" +
"}";
is(result, correct, "JS has been correctly prettified");
finishUp();
},
};
Scratchpad.addObserver(observer);
}, false);
}
});
info("Checking beautification");
helpers.setInput('jsb ' + TEST_URI);
/*
helpers.check({
input: 'jsb',
hints: ' [options]',
markup: 'VVV',
status: 'VALID'
DeveloperToolbarTest.exec({
typed: "jsb " + TEST_URI,
completed: false
});
*/
DeveloperToolbarTest.exec({ completed: false });
}
let finishUp = DeveloperToolbarTest.checkCalled(function GJT_finishUp() {
if (scratchpadWin) {
scratchpadWin.close();
scratchpadWin = null;
}
});

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

@ -1 +1,2 @@
function somefunc(){for(let n=0;n<500;n++){if(n%2==1){console.log(n);console.log(n+1);}}}
function somefunc(){if (true) // Some comment
doSomething();for(let n=0;n<500;n++){if(n%2==1){console.log(n);console.log(n+1);}}}

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

@ -1,804 +0,0 @@
/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2007, Parakey Inc.
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* * Neither the name of Parakey Inc. nor the names of its
* contributors may be used to endorse or promote products
* derived from this software without specific prior
* written permission of Parakey Inc.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Creator:
* Joe Hewitt
* Contributors
* John J. Barton (IBM Almaden)
* Jan Odvarko (Mozilla Corp.)
* Max Stepanov (Aptana Inc.)
* Rob Campbell (Mozilla Corp.)
* Hans Hillen (Paciello Group, Mozilla)
* Curtis Bartley (Mozilla Corp.)
* Mike Collins (IBM Almaden)
* Kevin Decker
* Mike Ratcliffe (Comartis AG)
* Hernan Rodríguez Colmeiro
* Austin Andrews
* Christoph Dorn
* Steven Roussey (AppCenter Inc, Network54)
*/
///////////////////////////////////////////////////////////////////////////
//// InsideOutBox
/**
* InsideOutBoxView is a simple interface definition for views implementing
* InsideOutBox controls. All implementors must define these methods.
* Implemented in InspectorUI.
*/
/*
InsideOutBoxView = {
//
* Retrieves the parent object for a given child object.
* @param aChild
* The child node to retrieve the parent object for.
* @returns a DOM node | null
//
getParentObject: function(aChild) {},
//
* Retrieves a given child node.
*
* If both index and previousSibling are passed, the implementation
* may assume that previousSibling will be the return for getChildObject
* with index-1.
* @param aParent
* The parent object of the child object to retrieve.
* @param aIndex
* The index of the child object to retrieve from aParent.
* @param aPreviousSibling
* The previous sibling of the child object to retrieve.
* Supercedes aIndex.
* @returns a DOM object | null
//
getChildObject: function(aParent, aIndex, aPreviousSibling) {},
//
* Renders the HTML representation of the object. Should return an HTML
* object which will be displayed to the user.
* @param aObject
* The object to create the box object for.
* @param aIsRoot
* Is the object the root object. May not be used in all
* implementations.
* @returns an object box | null
//
createObjectBox: function(aObject, aIsRoot) {},
//
* Convenience wrappers for classList API.
* @param aObject
* DOM node to query/set.
* @param aClassName
* String containing the class name to query/set.
//
hasClass: function(aObject, aClassName) {},
addClass: function(aObject, aClassName) {},
removeClass: function(aObject, aClassName) {}
};
*/
/**
* Creates a tree based on objects provided by a separate "view" object.
*
* Construction uses an "inside-out" algorithm, meaning that the view's job is
* first to tell us the ancestry of each object, and secondarily its
* descendants.
*
* Constructor
* @param aView
* The view requiring the InsideOutBox.
* @param aBox
* The box object containing the InsideOutBox. Required to add/remove
* children during box manipulation (toggling opened or closed).
*/
var EXPORTED_SYMBOLS = ["InsideOutBox"];
const Cu = Components.utils;
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
function InsideOutBox(aView, aBox)
{
this.view = aView;
this.box = aBox;
this.rootObject = null;
this.rootObjectBox = null;
this.selectedObjectBox = null;
this.highlightedObjectBox = null;
this.scrollIntoView = false;
};
InsideOutBox.prototype =
{
/**
* Highlight the given object node in the tree.
* @param aObject
* the object to highlight.
* @returns objectBox
*/
highlight: function IOBox_highlight(aObject)
{
let objectBox = this.createObjectBox(aObject);
this.highlightObjectBox(objectBox);
return objectBox;
},
/**
* Open the given object node in the tree.
* @param aObject
* The object node to open.
* @returns objectBox
*/
openObject: function IOBox_openObject(aObject)
{
let object = aObject;
let firstChild = this.view.getChildObject(object, 0);
if (firstChild)
object = firstChild;
return this.openToObject(object);
},
/**
* Open the tree up to the given object node.
* @param aObject
* The object in the tree to open to.
* @returns objectBox
*/
openToObject: function IOBox_openToObject(aObject)
{
let objectBox = this.createObjectBox(aObject);
this.openObjectBox(objectBox);
return objectBox;
},
/**
* Select the given object node in the tree.
* @param aObject
* The object node to select.
* @param makeBoxVisible
* Boolean. Open the object box in the tree?
* @param forceOpen
* Force the object box open by expanding all elements in the tree?
* @param scrollIntoView
* Scroll the objectBox into view?
* @returns nsIDOMNode|null
* A DOM node that represents the "object box", the element that
* holds/displays the given aObject representation in the tree. If
* the object cannot be selected, if it is a stale object, null is
* returned.
*/
select:
function IOBox_select(aObject, makeBoxVisible, forceOpen, scrollIntoView)
{
let objectBox = this.createObjectBox(aObject);
if (!objectBox) {
return null;
}
this.selectObjectBox(objectBox, forceOpen);
if (makeBoxVisible) {
this.openObjectBox(objectBox);
}
if (scrollIntoView) {
// We want to center the label of the element, not the whole tag
// (which includes all of its children, and is vertically huge).
LayoutHelpers.scrollIntoViewIfNeeded(objectBox.firstElementChild);
}
return objectBox;
},
/**
* Expands/contracts the given object, depending on its state.
* @param aObject
* The tree node to expand/contract.
*/
toggleObject: function IOBox_toggleObject(aObject)
{
let box = this.createObjectBox(aObject);
if (!(this.view.hasClass(box, "open")))
this.expandObjectBox(box);
else
this.contractObjectBox(box);
},
/**
* Expand the given object in the tree.
* @param aObject
* The tree node to expand.
*/
expandObject: function IOBox_expandObject(aObject)
{
let objectBox = this.createObjectBox(aObject);
if (objectBox)
this.expandObjectBox(objectBox);
},
/**
* Contract the given object in the tree.
* @param aObject
* The tree node to contract.
*/
contractObject: function IOBox_contractObject(aObject)
{
let objectBox = this.createObjectBox(aObject);
if (objectBox)
this.contractObjectBox(objectBox);
},
/**
* General method for iterating over an object's ancestors and performing
* some function.
* @param aObject
* The object whose ancestors we wish to iterate over.
* @param aCallback
* The function to call with the object as argument.
*/
iterateObjectAncestors: function IOBox_iterateObjectAncesors(aObject, aCallback)
{
let object = aObject;
if (!(aCallback && typeof(aCallback) == "function")) {
this.view._log("Illegal argument in IOBox.iterateObjectAncestors");
return;
}
while ((object = this.getParentObjectBox(object)))
aCallback(object);
},
/**
* Highlight the given objectBox in the tree.
* @param aObjectBox
* The objectBox to highlight.
*/
highlightObjectBox: function IOBox_highlightObjectBox(aObjectBox)
{
let self = this;
if (!aObjectBox)
return;
if (this.highlightedObjectBox) {
this.view.removeClass(this.highlightedObjectBox, "highlighted");
this.iterateObjectAncestors(this.highlightedObjectBox, function (box) {
self.view.removeClass(box, "highlightOpen");
});
}
this.highlightedObjectBox = aObjectBox;
this.view.addClass(aObjectBox, "highlighted");
this.iterateObjectAncestors(this.highlightedObjectBox, function (box) {
self.view.addClass(box, "highlightOpen");
});
aObjectBox.scrollIntoView(true);
},
/**
* Select the given objectBox in the tree, forcing it to be open if necessary.
* @param aObjectBox
* The objectBox to select.
* @param forceOpen
* Force the box (subtree) to be open?
*/
selectObjectBox: function IOBox_selectObjectBox(aObjectBox, forceOpen)
{
let isSelected = this.selectedObjectBox &&
aObjectBox == this.selectedObjectBox;
// aObjectBox is already selected, return
if (isSelected)
return;
if (this.selectedObjectBox)
this.view.removeClass(this.selectedObjectBox, "selected");
this.selectedObjectBox = aObjectBox;
if (aObjectBox) {
this.view.addClass(aObjectBox, "selected");
// Force it open the first time it is selected
if (forceOpen)
this.expandObjectBox(aObjectBox, true);
}
},
/**
* Returns the next object box in the tree for navigation purposes.
*/
nextObjectBox: function IOBox_nextObjectBox(aBoxObject)
{
let candidate;
let boxObject = aBoxObject || this.selectedObjectBox;
if (!boxObject)
return this.rootObjectBox;
// If expanded, return the first child.
let isOpen = this.view.hasClass(boxObject, "open");
let childObjectBox = this.getChildObjectBox(boxObject);
if (isOpen && childObjectBox && childObjectBox.firstChild) {
candidate = childObjectBox.firstChild;
} else {
// Otherwise we get the next available sibling.
while (boxObject) {
if (boxObject.nextSibling) {
boxObject = boxObject.nextSibling;
break;
}
boxObject = this.getParentObjectBox(boxObject);
}
candidate = boxObject;
}
// If the node is not an element (comments or text nodes), we
// jump to the next line.
if (candidate &&
candidate.repObject.nodeType != candidate.repObject.ELEMENT_NODE) {
return this.nextObjectBox(candidate);
}
return candidate;
},
/**
* Returns the next object in the tree for navigation purposes.
*/
nextObject: function IOBox_nextObject()
{
let next = this.nextObjectBox();
return next ? next.repObject : null;
},
/**
* Returns the object that is below the selection.
*
* @param aDistance Number of lines to jump.
*/
farNextObject: function IOBox_farPreviousProject(aDistance)
{
let boxObject = this.selectedObjectBox;
while (aDistance-- > 0) {
let newBoxObject = this.nextObjectBox(boxObject);
if (!newBoxObject) {
break;
}
boxObject = newBoxObject;
}
return boxObject ? boxObject.repObject : null;
},
/**
* Returns the last visible child box of an object box.
*/
lastVisible: function IOBox_lastVisibleChild(aNode)
{
if (!this.view.hasClass(aNode, "open"))
return aNode;
let childBox = this.getChildObjectBox(aNode);
if (!childBox || !childBox.lastChild)
return aNode;
return this.lastVisible(childBox.lastChild);
},
/**
* Returns the previous object box in the tree for navigation purposes.
*/
previousObjectBox: function IOBox_previousObjectBox(aBoxObject)
{
let boxObject = aBoxObject || this.selectedObjectBox;
if (!boxObject)
return this.rootObjectBox;
let candidate;
let sibling = boxObject.previousSibling;
if (sibling) {
candidate = this.lastVisible(sibling);
} else {
candidate = this.getParentObjectBox(boxObject);
}
// If the node is not an element (comments or text nodes), we
// jump to the previous line.
if (candidate &&
candidate.repObject.nodeType != candidate.repObject.ELEMENT_NODE) {
return this.previousObjectBox(candidate);
}
return candidate;
},
/**
* Returns the previous object in the tree for navigation purposes.
*/
previousObject: function IOBox_previousObject()
{
let boxObject = this.previousObjectBox();
return boxObject ? boxObject.repObject : null;
},
/**
* Returns the object that is above the selection.
*
* @param aDistance Number of lines to jump.
*/
farPreviousObject: function IOBox_farPreviousProject(aDistance)
{
let boxObject = this.selectedObjectBox;
while (aDistance-- > 0) {
let newBoxObject = this.previousObjectBox(boxObject);
if (!newBoxObject) {
break;
}
boxObject = newBoxObject;
if (boxObject === this.rootObjectBox)
break;
}
return boxObject ? boxObject.repObject : null;
},
/**
* Open the ancestors of the given object box.
* @param aObjectBox
* The object box to open.
*/
openObjectBox: function IOBox_openObjectBox(aObjectBox)
{
if (!aObjectBox)
return;
let self = this;
this.iterateObjectAncestors(aObjectBox, function (box) {
self.view.addClass(box, "open");
let labelBox = box.querySelector(".nodeLabelBox");
if (labelBox)
labelBox.setAttribute("aria-expanded", "true");
});
},
/**
* Expand the given object box.
* @param aObjectBox
* The object box to expand.
*/
expandObjectBox: function IOBox_expandObjectBox(aObjectBox)
{
let nodeChildBox = this.getChildObjectBox(aObjectBox);
// no children means nothing to expand, return
if (!nodeChildBox)
return;
if (!aObjectBox.populated) {
let firstChild = this.view.getChildObject(aObjectBox.repObject, 0);
this.populateChildBox(firstChild, nodeChildBox);
}
let labelBox = aObjectBox.querySelector(".nodeLabelBox");
if (labelBox)
labelBox.setAttribute("aria-expanded", "true");
this.view.addClass(aObjectBox, "open");
},
/**
* Contract the given object box.
* @param aObjectBox
* The object box to contract.
*/
contractObjectBox: function IOBox_contractObjectBox(aObjectBox)
{
this.view.removeClass(aObjectBox, "open");
let nodeLabel = aObjectBox.querySelector(".nodeLabel");
let labelBox = nodeLabel.querySelector(".nodeLabelBox");
if (labelBox)
labelBox.setAttribute("aria-expanded", "false");
},
/**
* Toggle the given object box, forcing open if requested.
* @param aObjectBox
* The object box to toggle.
* @param forceOpen
* Force the objectbox open?
*/
toggleObjectBox: function IOBox_toggleObjectBox(aObjectBox, forceOpen)
{
let isOpen = this.view.hasClass(aObjectBox, "open");
if (!forceOpen && isOpen)
this.contractObjectBox(aObjectBox);
else if (!isOpen)
this.expandObjectBox(aObjectBox);
},
/**
* Creates all of the boxes for an object, its ancestors, and siblings.
* @param aObject
* The tree node to create the object boxes for.
* @returns anObjectBox or null
*/
createObjectBox: function IOBox_createObjectBox(aObject)
{
if (!aObject)
return null;
this.rootObject = this.getRootNode(aObject) || aObject;
// Get or create all of the boxes for the target and its ancestors
let objectBox = this.createObjectBoxes(aObject, this.rootObject);
if (!objectBox)
return null;
if (aObject == this.rootObject)
return objectBox;
return this.populateChildBox(aObject, objectBox.parentNode);
},
/**
* Creates all of the boxes for an object, its ancestors, and siblings up to
* a root.
* @param aObject
* The tree's object node to create the object boxes for.
* @param aRootObject
* The root object at which to stop building object boxes.
* @returns an object box or null
*/
createObjectBoxes: function IOBox_createObjectBoxes(aObject, aRootObject)
{
if (!aObject)
return null;
if (aObject == aRootObject) {
if (!this.rootObjectBox || this.rootObjectBox.repObject != aRootObject) {
if (this.rootObjectBox) {
try {
this.box.removeChild(this.rootObjectBox);
} catch (exc) {
this.view._log("this.box.removeChild(this.rootObjectBox) FAILS " +
this.box + " must not contain " + this.rootObjectBox);
}
}
this.highlightedObjectBox = null;
this.selectedObjectBox = null;
this.rootObjectBox = this.view.createObjectBox(aObject, true);
this.box.appendChild(this.rootObjectBox);
}
return this.rootObjectBox;
}
let parentNode = this.view.getParentObject(aObject);
let parentObjectBox = this.createObjectBoxes(parentNode, aRootObject);
if (!parentObjectBox)
return null;
let parentChildBox = this.getChildObjectBox(parentObjectBox);
if (!parentChildBox)
return null;
let childObjectBox = this.findChildObjectBox(parentChildBox, aObject);
return childObjectBox ? childObjectBox
: this.populateChildBox(aObject, parentChildBox);
},
/**
* Locate the object box for a given object node.
* @param aObject
* The given object node in the tree.
* @returns an object box or null.
*/
findObjectBox: function IOBox_findObjectBox(aObject)
{
if (!aObject)
return null;
if (aObject == this.rootObject)
return this.rootObjectBox;
let parentNode = this.view.getParentObject(aObject);
let parentObjectBox = this.findObjectBox(parentNode);
if (!parentObjectBox)
return null;
let parentChildBox = this.getChildObjectBox(parentObjectBox);
if (!parentChildBox)
return null;
return this.findChildObjectBox(parentChildBox, aObject);
},
getAncestorByClass: function IOBox_getAncestorByClass(node, className)
{
for (let parent = node; parent; parent = parent.parentNode) {
if (this.view.hasClass(parent, className))
return parent;
}
return null;
},
/**
* We want all children of the parent of repObject.
*/
populateChildBox: function IOBox_populateChildBox(repObject, nodeChildBox)
{
if (!repObject)
return null;
let parentObjectBox = this.getAncestorByClass(nodeChildBox, "nodeBox");
if (parentObjectBox.populated)
return this.findChildObjectBox(nodeChildBox, repObject);
let lastSiblingBox = this.getChildObjectBox(nodeChildBox);
let siblingBox = nodeChildBox.firstChild;
let targetBox = null;
let view = this.view;
let targetSibling = null;
let parentNode = view.getParentObject(repObject);
for (let i = 0; 1; ++i) {
targetSibling = view.getChildObject(parentNode, i, targetSibling);
if (!targetSibling)
break;
// Check if we need to start appending, or continue to insert before
if (lastSiblingBox && lastSiblingBox.repObject == targetSibling)
lastSiblingBox = null;
if (!siblingBox || siblingBox.repObject != targetSibling) {
let newBox = view.createObjectBox(targetSibling);
if (newBox) {
if (lastSiblingBox)
nodeChildBox.insertBefore(newBox, lastSiblingBox);
else
nodeChildBox.appendChild(newBox);
}
siblingBox = newBox;
}
if (targetSibling == repObject)
targetBox = siblingBox;
if (siblingBox && siblingBox.repObject == targetSibling)
siblingBox = siblingBox.nextSibling;
}
if (targetBox)
parentObjectBox.populated = true;
return targetBox;
},
/**
* Get the parent object box of a given object box.
* @params aObjectBox
* The object box of the parent.
* @returns an object box or null
*/
getParentObjectBox: function IOBox_getParentObjectBox(aObjectBox)
{
let parent = aObjectBox.parentNode ? aObjectBox.parentNode.parentNode : null;
return parent && parent.repObject ? parent : null;
},
/**
* Get the child object box of a given object box.
* @param aObjectBox
* The object box whose child you want.
* @returns an object box or null
*/
getChildObjectBox: function IOBox_getChildObjectBox(aObjectBox)
{
return aObjectBox.querySelector(".nodeChildBox");
},
/**
* Find the child object box for a given repObject within the subtree
* rooted at aParentNodeBox.
* @param aParentNodeBox
* root of the subtree in which to search for repObject.
* @param aRepObject
* The object you wish to locate in the subtree.
* @returns an object box or null
*/
findChildObjectBox: function IOBox_findChildObjectBox(aParentNodeBox, aRepObject)
{
let childBox = aParentNodeBox.firstChild;
while (childBox) {
if (childBox.repObject == aRepObject)
return childBox;
childBox = childBox.nextSibling;
}
return null; // not found
},
/**
* Determines if the given node is an ancestor of the current root.
* @param aNode
* The node to look for within the tree.
* @returns boolean
*/
isInExistingRoot: function IOBox_isInExistingRoot(aNode)
{
let parentNode = aNode;
while (parentNode && parentNode != this.rootObject) {
parentNode = this.view.getParentObject(parentNode);
}
return parentNode == this.rootObject;
},
/**
* Get the root node of a given node.
* @param aNode
* The node whose root you wish to retrieve.
* @returns a root node or null
*/
getRootNode: function IOBox_getRootNode(aNode)
{
let node = aNode;
let tmpNode;
while ((tmpNode = this.view.getParentObject(node)))
node = tmpNode;
return node;
},
/**
* Clean up our mess.
*/
destroy: function IOBox_destroy()
{
delete this.view;
delete this.box;
delete this.rootObject;
delete this.rootObjectBox;
delete this.selectedObjectBox;
delete this.highlightedObjectBox;
delete this.scrollIntoView;
}
};

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

@ -11,9 +11,6 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \
domplate.jsm \
InsideOutBox.jsm \
TreePanel.jsm \
highlighter.jsm \
$(NULL)

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

@ -1,844 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource:///modules/domplate.jsm");
Cu.import("resource:///modules/InsideOutBox.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/inspector.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
const INSPECTOR_URI = "chrome://browser/content/inspector.html";
/**
* TreePanel
* A container for the Inspector's HTML Tree Panel widget constructor function.
* @param aContext nsIDOMWindow (xulwindow)
* @param aIUI global InspectorUI object
*/
function TreePanel(aContext, aIUI) {
this._init(aContext, aIUI);
};
TreePanel.prototype = {
showTextNodesWithWhitespace: false,
id: "treepanel", // DO NOT LOCALIZE
_open: false,
/**
* The tree panel container element.
* @returns xul:panel|xul:vbox|null
* xul:panel is returned when the tree panel is not docked, or
* xul:vbox when when the tree panel is docked.
* null is returned when no container is available.
*/
get container()
{
return this.document.getElementById("inspector-tree-box");
},
/**
* Main TreePanel boot-strapping method. Initialize the TreePanel with the
* originating context and the InspectorUI global.
* @param aContext nsIDOMWindow (xulwindow)
* @param aIUI global InspectorUI object
*/
_init: function TP__init(aContext, aIUI)
{
this.IUI = aIUI;
this.window = aContext;
this.document = this.window.document;
this.button =
this.IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton");
domplateUtils.setDOM(this.window);
this.DOMHelpers = new DOMHelpers(this.window);
let isOpen = this.isOpen.bind(this);
this.editingEvents = {};
},
/**
* Initialization function for the TreePanel.
*/
initializeIFrame: function TP_initializeIFrame()
{
if (!this.initializingTreePanel || this.treeLoaded) {
return;
}
this.treeBrowserDocument = this.treeIFrame.contentDocument;
this.treePanelDiv = this.treeBrowserDocument.createElement("div");
this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
this.treePanelDiv.ownerPanel = this;
this.ioBox = new InsideOutBox(this, this.treePanelDiv);
this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
this.treeLoaded = true;
this._boundTreeKeyPress = this.onTreeKeyPress.bind(this);
this.treeIFrame.addEventListener("keypress", this._boundTreeKeyPress.bind(this), true);
this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
this.treeIFrame.focus();
delete this.initializingTreePanel;
Services.obs.notifyObservers(null,
this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
if (this.pendingSelection) {
this.select(this.pendingSelection.node, this.pendingSelection.scroll);
delete this.pendingSelection;
}
},
/**
* Open the inspector's tree panel and initialize it.
*/
open: function TP_open()
{
if (this._open) {
return;
}
this._open = true;
this.button.setAttribute("checked", true);
this.initializingTreePanel = true;
this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
if (!this.treeIFrame) {
this.treeIFrame = this.document.createElement("iframe");
this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
this.treeIFrame.flex = 1;
this.treeIFrame.setAttribute("type", "content");
this.treeIFrame.setAttribute("context", "inspector-node-popup");
}
let treeBox = null;
treeBox = this.document.createElement("vbox");
treeBox.id = "inspector-tree-box";
treeBox.state = "open";
try {
treeBox.height =
Services.prefs.getIntPref("devtools.inspector.htmlHeight");
} catch(e) {
treeBox.height = 112;
}
treeBox.minHeight = 64;
this.splitter = this.document.createElement("splitter");
this.splitter.id = "inspector-tree-splitter";
this.splitter.className = "devtools-horizontal-splitter";
let container = this.document.getElementById("appcontent");
container.appendChild(this.splitter);
container.appendChild(treeBox);
treeBox.appendChild(this.treeIFrame);
this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
{
this.treeIFrame.removeEventListener("load",
this._boundLoadedInitializeTreePanel, true);
delete this._boundLoadedInitializeTreePanel;
this.initializeIFrame();
}.bind(this);
this.treeIFrame.addEventListener("load",
this._boundLoadedInitializeTreePanel, true);
let src = this.treeIFrame.getAttribute("src");
if (src != INSPECTOR_URI) {
this.treeIFrame.setAttribute("src", INSPECTOR_URI);
} else {
this.treeIFrame.contentWindow.location.reload();
}
},
/**
* Close the TreePanel.
*/
close: function TP_close()
{
this._open = false;
// Stop caring about the tree iframe load if it's in progress.
if (this._boundLoadedInitializeTreePanel) {
this.treeIFrame.removeEventListener("load",
this._boundLoadedInitializeTreePanel, true);
delete this._boundLoadedInitializeTreePanel;
}
this.button.removeAttribute("checked");
let treeBox = this.container;
Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
let treeBoxParent = treeBox.parentNode;
treeBoxParent.removeChild(this.splitter);
treeBoxParent.removeChild(treeBox);
if (this.treePanelDiv) {
this.treePanelDiv.ownerPanel = null;
let parent = this.treePanelDiv.parentNode;
parent.removeChild(this.treePanelDiv);
this.treeIFrame.removeEventListener("keypress", this._boundTreeKeyPress, true);
delete this.treePanelDiv;
delete this.treeBrowserDocument;
}
if (this.ioBox) {
this.ioBox.destroy();
delete this.ioBox;
}
this.treeLoaded = false;
},
/**
* Is the TreePanel open?
* @returns boolean
*/
isOpen: function TP_isOpen()
{
return this._open;
},
/**
* Toggle the TreePanel.
*/
toggle: function TP_toggle()
{
this.isOpen() ? this.close() : this.open();
},
/**
* Create the ObjectBox for the given object.
* @param object nsIDOMNode
* @param isRoot boolean - Is this the root object?
* @returns InsideOutBox
*/
createObjectBox: function TP_createObjectBox(object, isRoot)
{
let tag = domplateUtils.getNodeTag(object);
if (tag)
return tag.replace({object: object}, this.treeBrowserDocument);
},
getParentObject: function TP_getParentObject(node)
{
return this.DOMHelpers.getParentObject(node);
},
getChildObject: function TP_getChildObject(node, index, previousSibling)
{
return this.DOMHelpers.getChildObject(node, index, previousSibling,
this.showTextNodesWithWhitespace);
},
getFirstChild: function TP_getFirstChild(node)
{
return this.DOMHelpers.getFirstChild(node);
},
getNextSibling: function TP_getNextSibling(node)
{
return this.DOMHelpers.getNextSibling(node);
},
/////////////////////////////////////////////////////////////////////
// Event Handling
/**
* Handle click events in the html tree panel.
* @param aEvent
* The mouse event.
*/
onTreeClick: function TP_onTreeClick(aEvent)
{
let node;
let target = aEvent.target;
let hitTwisty = false;
if (this.hasClass(target, "twisty")) {
node = this.getRepObject(aEvent.target.nextSibling);
hitTwisty = true;
} else {
node = this.getRepObject(aEvent.target);
}
if (node) {
if (hitTwisty) {
this.ioBox.toggleObject(node);
} else {
if (this.IUI.inspecting) {
this.IUI.stopInspecting(true);
} else {
this.navigate(node);
}
}
}
},
/**
* Handle double-click events in the html tree panel.
* Double-clicking an attribute name or value allows it to be edited.
* @param aEvent
* The mouse event.
*/
onTreeDblClick: function TP_onTreeDblClick(aEvent)
{
// if already editing an attribute value, double-clicking elsewhere
// in the tree is the same as a click, which dismisses the editor
if (this.editingContext)
this.closeEditor();
let target = aEvent.target;
if (!this.hasClass(target, "editable")) {
return;
}
let repObj = this.getRepObject(target);
if (this.hasClass(target, "nodeValue")) {
let attrName = target.getAttribute("data-attributeName");
let attrVal = target.innerHTML;
this.editAttribute(target, repObj, attrName, attrVal);
}
if (this.hasClass(target, "nodeName")) {
let attrName = target.innerHTML;
let attrValNode = target.nextSibling.nextSibling; // skip 2 (=)
if (attrValNode)
this.editAttribute(target, repObj, attrName, attrValNode.innerHTML);
}
},
navigate: function TP_navigate(node)
{
if (!node)
return;
this.ioBox.select(node, false, false, true);
if (this.IUI.highlighter.isNodeHighlightable(node)) {
this.IUI.select(node, true, false, "treepanel");
this.IUI.highlighter.highlight(node);
}
},
onTreeKeyPress: function TP_onTreeKeyPress(aEvent)
{
let handled = true;
switch(aEvent.keyCode) {
case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
this.ioBox.contractObjectBox(this.ioBox.selectedObjectBox);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
this.ioBox.expandObjectBox(this.ioBox.selectedObjectBox);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_UP:
this.navigate(this.ioBox.previousObject());
break;
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
this.navigate(this.ioBox.nextObject());
break;
case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
this.navigate(this.ioBox.farPreviousObject(10));
break;
case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
this.navigate(this.ioBox.farNextObject(10));
break;
case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
this.navigate(this.ioBox.rootObject);
break;
default:
handled = false;
}
if (handled) {
aEvent.stopPropagation();
aEvent.preventDefault();
}
},
/**
* Starts the editor for an attribute name or value.
* @param aAttrObj
* The DOM object representing the attribute name or value in the HTML
* Tree.
* @param aRepObj
* The original DOM (target) object being inspected/edited
* @param aAttrName
* The name of the attribute being edited
* @param aAttrVal
* The current value of the attribute being edited
*/
editAttribute:
function TP_editAttribute(aAttrObj, aRepObj, aAttrName, aAttrVal)
{
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
let editorInput =
this.treeBrowserDocument.getElementById("attribute-editor-input");
let attrDims = aAttrObj.getBoundingClientRect();
// figure out actual viewable viewport dimensions (sans scrollbars)
let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
// saves the editing context for use when the editor is saved/closed
this.editingContext = {
attrObj: aAttrObj,
repObj: aRepObj,
attrName: aAttrName,
attrValue: aAttrVal
};
// highlight attribute-value node in tree while editing
this.addClass(aAttrObj, "editingAttributeValue");
// show the editor
this.addClass(editor, "editing");
// offset the editor below the attribute-value node being edited
let editorVerticalOffset = 2;
// keep the editor comfortably within the bounds of the viewport
let editorViewportBoundary = 5;
// outer editor is sized based on the <input> box inside it
editorInput.style.width = Math.min(attrDims.width, viewportWidth -
editorViewportBoundary) + "px";
editorInput.style.height = Math.min(attrDims.height, viewportHeight -
editorViewportBoundary) + "px";
let editorDims = editor.getBoundingClientRect();
// calculate position for the editor according to the attribute node
let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
// center the editor against the attribute value
((editorDims.width - attrDims.width) / 2);
let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
attrDims.height + editorVerticalOffset;
// but, make sure the editor stays within the visible viewport
editorLeft = Math.max(0, Math.min(
(this.treeIFrame.contentWindow.scrollX +
viewportWidth - editorDims.width),
editorLeft)
);
editorTop = Math.max(0, Math.min(
(this.treeIFrame.contentWindow.scrollY +
viewportHeight - editorDims.height),
editorTop)
);
// position the editor
editor.style.left = editorLeft + "px";
editor.style.top = editorTop + "px";
// set and select the text
if (this.hasClass(aAttrObj, "nodeValue")) {
editorInput.value = aAttrVal;
editorInput.select();
} else {
editorInput.value = aAttrName;
editorInput.select();
}
// listen for editor specific events
this.bindEditorEvent(editor, "click", function(aEvent) {
aEvent.stopPropagation();
});
this.bindEditorEvent(editor, "dblclick", function(aEvent) {
aEvent.stopPropagation();
});
this.bindEditorEvent(editor, "keypress",
this.handleEditorKeypress.bind(this));
// event notification
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED,
null);
},
/**
* Handle binding an event handler for the editor.
* (saves the callback for easier unbinding later)
* @param aEditor
* The DOM object for the editor
* @param aEventName
* The name of the event to listen for
* @param aEventCallback
* The callback to bind to the event (and also to save for later
* unbinding)
*/
bindEditorEvent:
function TP_bindEditorEvent(aEditor, aEventName, aEventCallback)
{
this.editingEvents[aEventName] = aEventCallback;
aEditor.addEventListener(aEventName, aEventCallback, false);
},
/**
* Handle unbinding an event handler from the editor.
* (unbinds the previously bound and saved callback)
* @param aEditor
* The DOM object for the editor
* @param aEventName
* The name of the event being listened for
*/
unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName)
{
aEditor.removeEventListener(aEventName, this.editingEvents[aEventName],
false);
this.editingEvents[aEventName] = null;
},
/**
* Handle keypress events in the editor.
* @param aEvent
* The keyboard event.
*/
handleEditorKeypress: function TP_handleEditorKeypress(aEvent)
{
if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) {
this.saveEditor();
aEvent.preventDefault();
aEvent.stopPropagation();
} else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) {
this.closeEditor();
aEvent.preventDefault();
aEvent.stopPropagation();
}
},
/**
* Close the editor and cleanup.
*/
closeEditor: function TP_closeEditor()
{
if (!this.treeBrowserDocument) // already closed, bug 706092
return;
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
let editorInput =
this.treeBrowserDocument.getElementById("attribute-editor-input");
// remove highlight from attribute-value node in tree
this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
// hide editor
this.removeClass(editor, "editing");
// stop listening for editor specific events
this.unbindEditorEvent(editor, "click");
this.unbindEditorEvent(editor, "dblclick");
this.unbindEditorEvent(editor, "keypress");
// clean up after the editor
editorInput.value = "";
editorInput.blur();
this.editingContext = null;
this.editingEvents = {};
// event notification
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
null);
},
/**
* Commit the edits made in the editor, then close it.
*/
saveEditor: function TP_saveEditor()
{
let editorInput =
this.treeBrowserDocument.getElementById("attribute-editor-input");
let dirty = false;
if (this.hasClass(this.editingContext.attrObj, "nodeValue")) {
// set the new attribute value on the original target DOM element
this.editingContext.repObj.setAttribute(this.editingContext.attrName,
editorInput.value);
// update the HTML tree attribute value
this.editingContext.attrObj.innerHTML = editorInput.value;
dirty = true;
}
if (this.hasClass(this.editingContext.attrObj, "nodeName")) {
// remove the original attribute from the original target DOM element
this.editingContext.repObj.removeAttribute(this.editingContext.attrName);
// set the new attribute value on the original target DOM element
this.editingContext.repObj.setAttribute(editorInput.value,
this.editingContext.attrValue);
// update the HTML tree attribute value
this.editingContext.attrObj.innerHTML = editorInput.value;
dirty = true;
}
this.IUI.isDirty = dirty;
this.IUI.nodeChanged("treepanel");
// event notification
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
null);
this.closeEditor();
},
/**
* Simple tree select method.
* @param aNode the DOM node in the content document to select.
* @param aScroll boolean scroll to the visible node?
*/
select: function TP_select(aNode, aScroll, aFrom)
{
if (this.ioBox) {
this.ioBox.select(aNode, true, aFrom != "treepanel", aScroll);
} else {
this.pendingSelection = { node: aNode, scroll: aScroll };
}
},
///////////////////////////////////////////////////////////////////////////
//// Utility functions
/**
* Does the given object have a class attribute?
* @param aNode
* the DOM node.
* @param aClass
* The class string.
* @returns boolean
*/
hasClass: function TP_hasClass(aNode, aClass)
{
if (!(aNode instanceof this.window.Element))
return false;
return aNode.classList.contains(aClass);
},
/**
* Add the class name to the given object.
* @param aNode
* the DOM node.
* @param aClass
* The class string.
*/
addClass: function TP_addClass(aNode, aClass)
{
if (aNode instanceof this.window.Element)
aNode.classList.add(aClass);
},
/**
* Remove the class name from the given object
* @param aNode
* the DOM node.
* @param aClass
* The class string.
*/
removeClass: function TP_removeClass(aNode, aClass)
{
if (aNode instanceof this.window.Element)
aNode.classList.remove(aClass);
},
/**
* Get the "repObject" from the HTML panel's domplate-constructed DOM node.
* In this system, a "repObject" is the Object being Represented by the box
* object. It is the "real" object that we're building our facade around.
*
* @param element
* The element in the HTML panel the user clicked.
* @returns either a real node or null
*/
getRepObject: function TP_getRepObject(element)
{
let target = null;
for (let child = element; child; child = child.parentNode) {
if (this.hasClass(child, "repTarget"))
target = child;
if (child.repObject) {
if (!target && this.hasClass(child.repObject, "repIgnore"))
break;
else
return child.repObject;
}
}
return null;
},
/**
* Remove a node box from the tree view.
* @param aElement
* The DOM node to remove from the HTML IOBox.
*/
deleteChildBox: function TP_deleteChildBox(aElement)
{
let childBox = this.ioBox.findObjectBox(aElement);
if (!childBox) {
return;
}
childBox.parentNode.removeChild(childBox);
},
/**
* Destructor function. Cleanup.
*/
destroy: function TP_destroy()
{
if (this.isOpen()) {
this.close();
}
domplateUtils.setDOM(null);
if (this.DOMHelpers) {
this.DOMHelpers.destroy();
delete this.DOMHelpers;
}
if (this.treePanelDiv) {
this.treePanelDiv.ownerPanel = null;
let parent = this.treePanelDiv.parentNode;
parent.removeChild(this.treePanelDiv);
delete this.treePanelDiv;
delete this.treeBrowserDocument;
}
if (this.treeIFrame) {
this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
let parent = this.treeIFrame.parentNode;
parent.removeChild(this.treeIFrame);
delete this.treeIFrame;
}
}
};
/**
* DOMHelpers
* Makes DOM traversal easier. Goes through iframes.
*
* @constructor
* @param nsIDOMWindow aWindow
* The content window, owning the document to traverse.
*/
function DOMHelpers(aWindow) {
this.window = aWindow;
};
DOMHelpers.prototype = {
getParentObject: function Helpers_getParentObject(node)
{
let parentNode = node ? node.parentNode : null;
if (!parentNode) {
// Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
// and Notation. top level windows have no parentNode
if (node && node == this.window.Node.DOCUMENT_NODE) {
// document type
if (node.defaultView) {
let embeddingFrame = node.defaultView.frameElement;
if (embeddingFrame)
return embeddingFrame.parentNode;
}
}
// a Document object without a parentNode or window
return null; // top level has no parent
}
if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
if (parentNode.defaultView) {
return parentNode.defaultView.frameElement;
}
// parent is document element, but no window at defaultView.
return null;
}
if (!parentNode.localName)
return null;
return parentNode;
},
getChildObject: function Helpers_getChildObject(node, index, previousSibling,
showTextNodesWithWhitespace)
{
if (!node)
return null;
if (node.contentDocument) {
// then the node is a frame
if (index == 0) {
return node.contentDocument.documentElement; // the node's HTMLElement
}
return null;
}
if (node instanceof this.window.GetSVGDocument) {
let svgDocument = node.getSVGDocument();
if (svgDocument) {
// then the node is a frame
if (index == 0) {
return svgDocument.documentElement; // the node's SVGElement
}
return null;
}
}
let child = null;
if (previousSibling) // then we are walking
child = this.getNextSibling(previousSibling);
else
child = this.getFirstChild(node);
if (showTextNodesWithWhitespace)
return child;
for (; child; child = this.getNextSibling(child)) {
if (!this.isWhitespaceText(child))
return child;
}
return null; // we have no children worth showing.
},
getFirstChild: function Helpers_getFirstChild(node)
{
let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
this.treeWalker = node.ownerDocument.createTreeWalker(node,
SHOW_ALL, null, false);
return this.treeWalker.firstChild();
},
getNextSibling: function Helpers_getNextSibling(node)
{
let next = this.treeWalker.nextSibling();
if (!next)
delete this.treeWalker;
return next;
},
isWhitespaceText: function Helpers_isWhitespaceText(node)
{
return node.nodeType == this.window.Node.TEXT_NODE &&
!/[^\s]/.exec(node.nodeValue);
},
destroy: function Helpers_destroy()
{
delete this.window;
delete this.treeWalker;
}
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,18 +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/. -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/htmlpanel.css" type="text/css"/>
</head>
<body role="application">
<div id="attribute-editor">
<input id="attribute-editor-input" />
</div>
</body>
</html>

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

@ -13,12 +13,12 @@ var EXPORTED_SYMBOLS = ["InspectorUI"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/TreePanel.jsm");
Cu.import("resource:///modules/devtools/MarkupView.jsm");
Cu.import("resource:///modules/highlighter.jsm");
Cu.import("resource:///modules/devtools/LayoutView.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
// Inspector notifications dispatched through the nsIObserverService.
const INSPECTOR_NOTIFICATIONS = {

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

@ -3,11 +3,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
content/browser/inspector.html (highlighter/inspector.html)
content/browser/devtools/markup-view.xhtml (markupview/markup-view.xhtml)
content/browser/devtools/markup-view.css (markupview/markup-view.css)
content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml)
content/browser/devtools/HUDService-content.js (webconsole/HUDService-content.js)
content/browser/devtools/webconsole.js (webconsole/webconsole.js)
* content/browser/devtools/webconsole.xul (webconsole/webconsole.xul)
* content/browser/scratchpad.xul (scratchpad/scratchpad.xul)

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

@ -129,6 +129,8 @@ LayoutView.prototype = {
this.iframe.removeEventListener("keypress", this.bound_handleKeypress, true);
this.inspector.chromeWindow.removeEventListener("message", this.onMessage, true);
this.close();
this.sizeHeadingLabel = null;
this.sizeLabel = null;
this.iframe = null;
this.view.parentNode.removeChild(this.view);
},
@ -159,6 +161,10 @@ LayoutView.prototype = {
this.documentReady = true;
this.doc = this.iframe.contentDocument;
// Save reference to the labels displaying size of the node.
this.sizeLabel = this.doc.querySelector(".size > span");
this.sizeHeadingLabel = this.doc.getElementById("element-size");
// We can't do that earlier because open() and close() need to do stuff
// inside the iframe.
@ -299,10 +305,9 @@ LayoutView.prototype = {
let width = Math.round(clientRect.width);
let height = Math.round(clientRect.height);
let elt = this.doc.querySelector("#element-size");
let newLabel = width + "x" + height;
if (elt.textContent != newLabel) {
elt.textContent = newLabel;
if (this.sizeHeadingLabel.textContent != newLabel) {
this.sizeHeadingLabel.textContent = newLabel;
}
// If the view is closed, no need to do anything more.
@ -312,7 +317,6 @@ LayoutView.prototype = {
let style = this.browser.contentWindow.getComputedStyle(node);;
for (let i in this.map) {
let selector = this.map[i].selector;
let property = this.map[i].property;
this.map[i].value = parseInt(style.getPropertyValue(property));
}
@ -326,6 +330,10 @@ LayoutView.prototype = {
for (let i in this.map) {
let selector = this.map[i].selector;
let span = this.doc.querySelector(selector);
if (span.textContent.length > 0 &&
span.textContent == this.map[i].value) {
continue;
}
span.textContent = this.map[i].value;
}
@ -335,7 +343,10 @@ LayoutView.prototype = {
height -= this.map.borderTop.value + this.map.borderBottom.value +
this.map.paddingTop.value + this.map.paddingBottom.value;
this.doc.querySelector(".size > span").textContent = width + "x" + height;
let newValue = width + "x" + height;
if (this.sizeLabel.textContent != newValue) {
this.sizeLabel.textContent = newValue;
}
},
/**

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

@ -10,6 +10,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
var EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
@ -67,6 +68,13 @@ let ResponsiveUIManager = {
default:
}
},
get events() {
if (!this._eventEmitter) {
this._eventEmitter = new EventEmitter();
}
return this._eventEmitter;
},
}
let presets = [
@ -162,6 +170,7 @@ function ResponsiveUI(aWindow, aTab)
} catch(e) {}
switchToFloatingScrollbars(this.tab);
ResponsiveUIManager.events.emit("on", this.tab, this);
}
ResponsiveUI.prototype = {
@ -215,6 +224,7 @@ ResponsiveUI.prototype = {
this.stack.removeAttribute("responsivemode");
delete this.tab.__responsiveUI;
ResponsiveUIManager.events.emit("off", this.tab, this);
},
/**

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

@ -45,15 +45,23 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_responsiveui.js \
browser_responsiveruleview.js \
browser_responsive_cmd.js \
browser_responsivecomputedview.js \
browser_responsiveuiaddcustompreset.js \
head.js \
helpers.js \
$(NULL)
# Disabled on Mac for frequent intermittent failures
ifneq ($(OS_ARCH), Darwin)
_BROWSER_FILES += \
browser_responsiveui.js \
browser_responsiveuiaddcustompreset.js \
$(NULL)
else
$(warning browser_responsiveui.js is disabled on OS X for intermittent failures. Bug 798772) \
$(warning browser_responsiveuiaddcustompreset.js is disabled on OS X for intermittent failures. Bugs 798775, 798777)
endif
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)

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

@ -3,6 +3,7 @@
function test() {
let instance, widthBeforeClose, heightBeforeClose;
let events = ResponsiveUI.ResponsiveUIManager.events;
waitForExplicitFinish();
@ -16,8 +17,8 @@ function test() {
function startTest() {
document.getElementById("Tools:ResponsiveUI").removeAttribute("disabled");
events.once("on", function() {executeSoon(onUIOpen)});
synthesizeKeyFromKeyTag("key_responsiveUI");
executeSoon(onUIOpen);
}
function onUIOpen() {
@ -118,14 +119,13 @@ function test() {
widthBeforeClose = content.innerWidth;
heightBeforeClose = content.innerHeight;
events.once("off", function() {executeSoon(restart)});
EventUtils.synthesizeKey("VK_ESCAPE", {});
executeSoon(restart);
}
function restart() {
events.once("on", function() {executeSoon(onUIOpen2)});
synthesizeKeyFromKeyTag("key_responsiveUI");
executeSoon(onUIOpen2);
}
function onUIOpen2() {
@ -138,8 +138,8 @@ function test() {
is(content.innerWidth, widthBeforeClose, "width restored.");
is(content.innerHeight, heightBeforeClose, "height restored.");
events.once("off", function() {executeSoon(finishUp)});
EventUtils.synthesizeKey("VK_ESCAPE", {});
executeSoon(finishUp);
}
function finishUp() {

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

@ -0,0 +1,124 @@
/* 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 EXPORTED_SYMBOLS = ["DOMHelpers"];
/**
* DOMHelpers
* Makes DOM traversal easier. Goes through iframes.
*
* @constructor
* @param nsIDOMWindow aWindow
* The content window, owning the document to traverse.
*/
function DOMHelpers(aWindow) {
this.window = aWindow;
};
DOMHelpers.prototype = {
getParentObject: function Helpers_getParentObject(node)
{
let parentNode = node ? node.parentNode : null;
if (!parentNode) {
// Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
// and Notation. top level windows have no parentNode
if (node && node == this.window.Node.DOCUMENT_NODE) {
// document type
if (node.defaultView) {
let embeddingFrame = node.defaultView.frameElement;
if (embeddingFrame)
return embeddingFrame.parentNode;
}
}
// a Document object without a parentNode or window
return null; // top level has no parent
}
if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
if (parentNode.defaultView) {
return parentNode.defaultView.frameElement;
}
// parent is document element, but no window at defaultView.
return null;
}
if (!parentNode.localName)
return null;
return parentNode;
},
getChildObject: function Helpers_getChildObject(node, index, previousSibling,
showTextNodesWithWhitespace)
{
if (!node)
return null;
if (node.contentDocument) {
// then the node is a frame
if (index == 0) {
return node.contentDocument.documentElement; // the node's HTMLElement
}
return null;
}
if (node instanceof this.window.GetSVGDocument) {
let svgDocument = node.getSVGDocument();
if (svgDocument) {
// then the node is a frame
if (index == 0) {
return svgDocument.documentElement; // the node's SVGElement
}
return null;
}
}
let child = null;
if (previousSibling) // then we are walking
child = this.getNextSibling(previousSibling);
else
child = this.getFirstChild(node);
if (showTextNodesWithWhitespace)
return child;
for (; child; child = this.getNextSibling(child)) {
if (!this.isWhitespaceText(child))
return child;
}
return null; // we have no children worth showing.
},
getFirstChild: function Helpers_getFirstChild(node)
{
let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
this.treeWalker = node.ownerDocument.createTreeWalker(node,
SHOW_ALL, null, false);
return this.treeWalker.firstChild();
},
getNextSibling: function Helpers_getNextSibling(node)
{
let next = this.treeWalker.nextSibling();
if (!next)
delete this.treeWalker;
return next;
},
isWhitespaceText: function Helpers_isWhitespaceText(node)
{
return node.nodeType == this.window.Node.TEXT_NODE &&
!/[^\s]/.exec(node.nodeValue);
},
destroy: function Helpers_destroy()
{
delete this.window;
delete this.treeWalker;
}
};

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

@ -7,9 +7,7 @@
const EXPORTED_SYMBOLS = [ "DeveloperToolbar" ];
const NS_XHTML = "http://www.w3.org/1999/xhtml";
const WEBCONSOLE_CONTENT_SCRIPT_URL =
"chrome://browser/content/devtools/HUDService-content.js";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
@ -26,6 +24,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gcli",
XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
"resource:///modules/devtools/CmdCmd.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
/**
* Due to a number of panel bugs we need a way to check if we are running on
* Linux. See the comments for TooltipPanel and OutputPanel for further details.
@ -56,7 +57,8 @@ function DeveloperToolbar(aChromeWindow, aToolbarElement)
this._lastState = NOTIFICATIONS.HIDE;
this._pendingShowCallback = undefined;
this._pendingHide = false;
this._errorsCount = {};
this._errorsCount = Object.create(null);
this._errorListeners = Object.create(null);
this._webConsoleButton = this._doc
.getElementById("developer-toolbar-webconsole");
@ -88,9 +90,6 @@ const NOTIFICATIONS = {
*/
DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
DeveloperToolbar.prototype._contentMessageListeners =
["WebConsole:CachedMessages", "WebConsole:PageError"];
/**
* Is the toolbar open?
*/
@ -285,21 +284,18 @@ DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab)
return;
}
let messageManager = aTab.linkedBrowser.messageManager;
messageManager.loadFrameScript(WEBCONSOLE_CONTENT_SCRIPT_URL, true);
let window = aTab.linkedBrowser.contentWindow;
let listener = new PageErrorListener(window, {
onPageError: this._onPageError.bind(this, tabId),
});
listener.init();
this._errorListeners[tabId] = listener;
this._errorsCount[tabId] = 0;
this._contentMessageListeners.forEach(function(aName) {
messageManager.addMessageListener(aName, this);
}, this);
let messages = listener.getCachedMessages();
messages.forEach(this._onPageError.bind(this, tabId));
let message = {
features: ["PageError"],
cachedMessages: ["PageError"],
};
this.sendMessageToTab(aTab, "WebConsole:Init", message);
this._updateErrorsCount();
};
@ -319,14 +315,10 @@ DeveloperToolbar.prototype._stopErrorsCount = function DT__stopErrorsCount(aTab)
return;
}
this.sendMessageToTab(aTab, "WebConsole:Destroy", {});
let messageManager = aTab.linkedBrowser.messageManager;
this._contentMessageListeners.forEach(function(aName) {
messageManager.removeMessageListener(aName, this);
}, this);
this._errorListeners[tabId].destroy();
delete this._errorListeners[tabId];
delete this._errorsCount[tabId];
this._updateErrorsCount();
};
@ -434,61 +426,13 @@ DeveloperToolbar.prototype.handleEvent = function DT_handleEvent(aEvent)
};
/**
* The handler of messages received from the nsIMessageManager.
*
* @param object aMessage the message received from the content process.
*/
DeveloperToolbar.prototype.receiveMessage = function DT_receiveMessage(aMessage)
{
if (!aMessage.json || !(aMessage.json.hudId in this._errorsCount)) {
return;
}
let tabId = aMessage.json.hudId;
let errors = this._errorsCount[tabId];
switch (aMessage.name) {
case "WebConsole:PageError":
this._onPageError(tabId, aMessage.json.pageError);
break;
case "WebConsole:CachedMessages":
aMessage.json.messages.forEach(this._onPageError.bind(this, tabId));
break;
}
if (errors != this._errorsCount[tabId]) {
this._updateErrorsCount(tabId);
}
};
/**
* Send a message to the content process using the nsIMessageManager of the
* given tab.
*
* @param nsIDOMNode aTab the tab you want to send a message to.
* @param string aName the name of the message you want to send.
* @param object aMessage the message to send.
*/
DeveloperToolbar.prototype.sendMessageToTab =
function DT_sendMessageToTab(aTab, aName, aMessage)
{
let tabId = aTab.linkedPanel;
aMessage.hudId = tabId;
if (!("id" in aMessage)) {
aMessage.id = "DevToolbar-" + this.sequenceId;
}
aTab.linkedBrowser.messageManager.sendAsyncMessage(aName, aMessage);
};
/**
* Process a "WebConsole:PageError" message received from the given tab. This
* method counts the JavaScript exceptions received.
* Count a page error received for the currently selected tab. This
* method counts the JavaScript exceptions received and CSS errors/warnings.
*
* @private
* @param string aTabId the ID of the tab from where the page error comes.
* @param object aPageError the page error object received from the content
* process.
* @param object aPageError the page error object received from the
* PageErrorListener.
*/
DeveloperToolbar.prototype._onPageError =
function DT__onPageError(aTabId, aPageError)
@ -501,6 +445,7 @@ function DT__onPageError(aTabId, aPageError)
}
this._errorsCount[aTabId]++;
this._updateErrorsCount(aTabId);
};
/**
@ -638,7 +583,6 @@ function OutputPanel(aChromeDoc, aInput, aLoadCallback)
this._frame = aChromeDoc.createElementNS(NS_XHTML, "iframe");
this._frame.id = "gcli-output-frame";
this._frame.setAttribute("src", "chrome://browser/content/devtools/commandlineoutput.xhtml");
this._frame.setAttribute("flex", "1");
this._panel.appendChild(this._frame);
this.displayedOutput = undefined;
@ -674,6 +618,33 @@ OutputPanel.prototype._onload = function OP_onload()
}
};
/**
* Determine the scrollbar width in the current document.
*
* @private
*/
Object.defineProperty(OutputPanel.prototype, 'scrollbarWidth', {
get: function() {
if (this.__scrollbarWidth) {
return this.__scrollbarWidth;
}
let hbox = this.document.createElementNS(XUL_NS, "hbox");
hbox.setAttribute("style", "height: 0%; overflow: hidden");
let scrollbar = this.document.createElementNS(XUL_NS, "scrollbar");
scrollbar.setAttribute("orient", "vertical");
hbox.appendChild(scrollbar);
this.document.documentElement.appendChild(hbox);
this.__scrollbarWidth = scrollbar.clientWidth;
this.document.documentElement.removeChild(hbox);
return this.__scrollbarWidth;
},
enumerable: true
});
/**
* Prevent the popup from hiding if it is not permitted via this.canHide.
*/
@ -691,17 +662,15 @@ OutputPanel.prototype._onpopuphiding = function OP_onpopuphiding(aEvent)
*/
OutputPanel.prototype.show = function OP_show()
{
// This is nasty, but displaying the panel causes it to re-flow, which can
// change the size it should be, so we need to resize the iframe after the
// panel has displayed
this._panel.ownerDocument.defaultView.setTimeout(function() {
this._resize();
}.bind(this), 0);
if (isLinux) {
this.canHide = false;
}
// We need to reset the iframe size in order for future size calculations to
// be correct
this._frame.style.minHeight = this._frame.style.maxHeight = 0;
this._frame.style.minWidth = 0;
this._panel.openPopup(this._input, "before_start", 0, 0, false, false, null);
this._resize();
@ -718,8 +687,38 @@ OutputPanel.prototype._resize = function CLP_resize()
return
}
this._frame.height = this.document.body.scrollHeight;
this._frame.width = this._input.clientWidth + 2;
// Set max panel width to match any content with a max of the width of the
// browser window.
let maxWidth = this._panel.ownerDocument.documentElement.clientWidth;
let width = Math.min(maxWidth, this.document.documentElement.scrollWidth);
// Add scrollbar width to content size in case a scrollbar is needed.
width += this.scrollbarWidth;
// Set the width of the iframe.
this._frame.style.minWidth = width + "px";
// browserAdjustment is used to correct the panel height according to the
// browsers borders etc.
const browserAdjustment = 15;
// Set max panel height to match any content with a max of the height of the
// browser window.
let maxHeight =
this._panel.ownerDocument.documentElement.clientHeight - browserAdjustment;
let height = Math.min(maxHeight, this.document.documentElement.scrollHeight);
// Set the height of the iframe. Setting iframe.height does not work.
this._frame.style.minHeight = this._frame.style.maxHeight = height + "px";
// Set the height and width of the panel to match the iframe.
this._panel.sizeTo(width, height);
// Move the panel to the correct position in the case that it has been
// positioned incorrectly.
let screenX = this._input.boxObject.screenX;
let screenY = this._toolbar.boxObject.screenY;
this._panel.moveTo(screenX, screenY - height);
};
/**

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -47,8 +47,11 @@ function test() {
function addErrors() {
expectUncaughtException();
let button = content.document.querySelector("button");
EventUtils.synthesizeMouse(button, 2, 2, {}, content);
waitForFocus(function() {
let button = content.document.querySelector("button");
EventUtils.synthesizeMouse(button, 2, 2, {}, content);
}, content);
waitForValue({
name: "button shows one more error after click in page",

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -18,12 +18,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
var EXPORTED_SYMBOLS = ["HUDService"];
@ -59,9 +57,6 @@ const MINIMUM_PAGE_HEIGHT = 50;
// The default console height, as a ratio from the content window inner height.
const DEFAULT_CONSOLE_HEIGHT = 0.33;
// This script is inserted into the content process.
const CONTENT_SCRIPT_URL = "chrome://browser/content/devtools/HUDService-content.js";
// points to the file to load in the Web Console iframe.
const UI_IFRAME_URL = "chrome://browser/content/devtools/webconsole.xul";
@ -172,7 +167,10 @@ HUD_SERVICE.prototype =
let hud = this.getHudReferenceById(hudId);
let document = hud.chromeDocument;
hud.destroy();
hud.destroy(function() {
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
});
delete this.hudReferences[hudId];
@ -199,9 +197,6 @@ HUD_SERVICE.prototype =
contentWindow.focus();
HeadsUpDisplayUICommands.refreshCommand();
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
},
/**
@ -502,9 +497,11 @@ HUD_SERVICE.prototype =
function WebConsole(aTab)
{
this.tab = aTab;
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + this.tab.linkedPanel;
this._onIframeLoad = this._onIframeLoad.bind(this);
this._asyncRequests = {};
this._init();
this._initUI();
}
WebConsole.prototype = {
@ -514,6 +511,9 @@ WebConsole.prototype = {
*/
tab: null,
chromeWindow: null,
chromeDocument: null,
/**
* Getter for HUDService.lastFinishedRequestCallback.
*
@ -522,41 +522,12 @@ WebConsole.prototype = {
*/
get lastFinishedRequestCallback() HUDService.lastFinishedRequestCallback,
/**
* Track callback functions registered for specific async requests sent to
* the content process.
*
* @private
* @type object
*/
_asyncRequests: null,
/**
* Message names that the HUD listens for. These messages come from the remote
* Web Console content script.
*
* @private
* @type array
*/
_messageListeners: ["JSTerm:EvalObject", "WebConsole:ConsoleAPI",
"WebConsole:CachedMessages", "WebConsole:PageError", "JSTerm:EvalResult",
"JSTerm:AutocompleteProperties", "JSTerm:ClearOutput",
"JSTerm:InspectObject", "WebConsole:NetworkActivity",
"WebConsole:FileActivity", "WebConsole:LocationChange",
"JSTerm:NonNativeConsoleAPI"],
/**
* The xul:panel that holds the Web Console when it is positioned as a window.
* @type nsIDOMElement
*/
consolePanel: null,
/**
* The current tab location.
* @type string
*/
contentLocation: "",
/**
* Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement
@ -577,22 +548,6 @@ WebConsole.prototype = {
get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
/**
* Initialize the Web Console instance.
* @private
*/
_init: function WC__init()
{
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.messageManager = this.tab.linkedBrowser.messageManager;
this.hudId = "hud_" + this.tab.linkedPanel;
this.notificationBox = this.chromeDocument
.getElementById(this.tab.linkedPanel);
this._initUI();
},
/**
* Initialize the Web Console UI. This method sets up the iframe.
* @private
@ -627,7 +582,6 @@ WebConsole.prototype = {
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui = new this.iframeWindow.WebConsoleFrame(this, position);
this._setupMessageManager();
},
/**
@ -772,8 +726,8 @@ WebConsole.prototype = {
*/
getPanelTitle: function WC_getPanelTitle()
{
return l10n.getFormatStr("webConsoleWindowTitleAndURL",
[this.contentLocation]);
let url = this.ui ? this.ui.contentLocation : "";
return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
},
positions: {
@ -806,7 +760,7 @@ WebConsole.prototype = {
// get the node position index
let nodeIdx = this.positions[aPosition];
let nBox = this.notificationBox;
let nBox = this.chromeDocument.getElementById(this.tab.linkedPanel);
let node = nBox.childNodes[nodeIdx];
// check to see if console is already positioned in aPosition
@ -903,126 +857,24 @@ WebConsole.prototype = {
/**
* The clear output button handler.
* @private
*/
onClearButton: function WC_onClearButton()
_onClearButton: function WC__onClearButton()
{
this.ui.jsterm.clearOutput(true);
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
},
/**
* Setup the message manager used to communicate with the Web Console content
* script. This method loads the content script, adds the message listeners
* and initializes the connection to the content script.
*
* @private
*/
_setupMessageManager: function WC__setupMessageManager()
{
this.messageManager.loadFrameScript(CONTENT_SCRIPT_URL, true);
this._messageListeners.forEach(function(aName) {
this.messageManager.addMessageListener(aName, this.ui);
}, this);
let message = {
features: ["ConsoleAPI", "JSTerm", "PageError", "NetworkMonitor",
"LocationChange"],
cachedMessages: ["ConsoleAPI", "PageError"],
NetworkMonitor: { monitorFileActivity: true },
JSTerm: { notifyNonNativeConsoleAPI: true },
preferences: {
"NetworkMonitor.saveRequestAndResponseBodies":
this.ui.saveRequestAndResponseBodies,
},
};
this.sendMessageToContent("WebConsole:Init", message);
},
/**
* Callback method for when the Web Console initialization is complete. For
* now this method sends the web-console-created notification using the
* nsIObserverService.
*
* @private
*/
_onInitComplete: function WC__onInitComplete()
{
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-created", null);
},
/**
* Handler for messages that have an associated callback function. The
* this.sendMessageToContent() allows one to provide a function to be invoked
* when the content script replies to the message previously sent. This is the
* method that invokes the callback.
*
* @see this.sendMessageToContent
* @private
* @param object aResponse
* Message object received from the content script.
*/
_receiveMessageWithCallback:
function WC__receiveMessageWithCallback(aResponse)
{
if (aResponse.id in this._asyncRequests) {
let request = this._asyncRequests[aResponse.id];
request.callback(aResponse, request.message);
delete this._asyncRequests[aResponse.id];
}
else {
Cu.reportError("receiveMessageWithCallback response for stale request " +
"ID " + aResponse.id);
}
},
/**
* Send a message to the content script.
*
* @param string aName
* The name of the message you want to send.
*
* @param object aMessage
* The message object you want to send. This object needs to have no
* cyclic references and it needs to be JSON-stringifiable.
*
* @param function [aCallback]
* Optional function you want to have called when the content script
* replies to your message. Your callback receives two arguments:
* (1) the response object from the content script and (2) the message
* you sent to the content script (which is aMessage here).
*/
sendMessageToContent:
function WC_sendMessageToContent(aName, aMessage, aCallback)
{
aMessage.hudId = this.hudId;
if (!("id" in aMessage)) {
aMessage.id = "HUDChrome-" + HUDService.sequenceId();
}
if (aCallback) {
this._asyncRequests[aMessage.id] = {
name: aName,
message: aMessage,
callback: aCallback,
};
}
this.messageManager.sendAsyncMessage(aName, aMessage);
},
/**
* Handler for the "WebConsole:LocationChange" message. If the Web Console is
* Handler for page location changes. If the Web Console is
* opened in a panel the panel title is updated.
*
* @param object aMessage
* The message received from the content script. It needs to hold two
* properties: location and title.
* @param string aURI
* New page location.
* @param string aTitle
* New page title.
*/
onLocationChange: function WC_onLocationChange(aMessage)
onLocationChange: function WC_onLocationChange(aURI, aTitle)
{
this.contentLocation = aMessage.location;
if (this.consolePanel) {
this.consolePanel.label = this.getPanelTitle();
}
@ -1051,15 +903,13 @@ WebConsole.prototype = {
/**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.
*
* @param function [aOnDestroy]
* Optional function to invoke when the Web Console instance is
* destroyed.
*/
destroy: function WC_destroy()
destroy: function WC_destroy(aOnDestroy)
{
this.sendMessageToContent("WebConsole:Destroy", {});
this._messageListeners.forEach(function(aName) {
this.messageManager.removeMessageListener(aName, this.ui);
}, this);
// Make sure that the console panel does not try to call
// deactivateHUDForContext() again.
this.consoleWindowUnregisterOnHide = false;
@ -1072,24 +922,31 @@ WebConsole.prototype = {
}
}
let onDestroy = function WC_onDestroyUI() {
// Remove the iframe and the consolePanel if the Web Console is inside a
// floating panel.
if (this.consolePanel && this.consolePanel.parentNode) {
this.consolePanel.hidePopup();
this.consolePanel.parentNode.removeChild(this.consolePanel);
this.consolePanel = null;
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
if (this.splitter.parentNode) {
this.splitter.parentNode.removeChild(this.splitter);
}
aOnDestroy && aOnDestroy();
}.bind(this);
if (this.ui) {
this.ui.destroy();
this.ui.destroy(onDestroy);
}
// Remove the iframe and the consolePanel if the Web Console is inside a
// floating panel.
if (this.consolePanel && this.consolePanel.parentNode) {
this.consolePanel.hidePopup();
this.consolePanel.parentNode.removeChild(this.consolePanel);
this.consolePanel = null;
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
if (this.splitter.parentNode) {
this.splitter.parentNode.removeChild(this.splitter);
else {
onDestroy();
}
},
};

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

@ -13,10 +13,8 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \
HUDService.jsm \
PropertyPanel.jsm \
NetworkHelper.jsm \
NetworkPanel.jsm \
AutocompletePopup.jsm \
WebConsoleUtils.jsm \
$(NULL)
TEST_DIRS = test

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

@ -16,17 +16,16 @@ XPCOMUtils.defineLazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1",
"nsIMIMEService");
XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
"resource:///modules/NetworkHelper.jsm");
"resource://gre/modules/devtools/NetworkHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
var EXPORTED_SYMBOLS = ["NetworkPanel"];
@ -67,7 +66,6 @@ function NetworkPanel(aParent, aHttpActivity)
self.panel.parentNode.removeChild(self.panel);
self.panel = null;
self.iframe = null;
self.document = null;
self.httpActivity = null;
if (self.linkNode) {
@ -77,9 +75,17 @@ function NetworkPanel(aParent, aHttpActivity)
}, false);
// Set the document object and update the content once the panel is loaded.
this.panel.addEventListener("load", function onLoad() {
self.panel.removeEventListener("load", onLoad, true);
self.document = self.iframe.contentWindow.document;
this.iframe.addEventListener("load", function onLoad() {
if (!self.iframe) {
return;
}
self.iframe.removeEventListener("load", onLoad, true);
self.update();
}, true);
this.panel.addEventListener("popupshown", function onPopupShown() {
self.panel.removeEventListener("popupshown", onPopupShown, true);
self.update();
}, true);
@ -95,12 +101,6 @@ function NetworkPanel(aParent, aHttpActivity)
NetworkPanel.prototype =
{
/**
* Callback is called once the NetworkPanel is processed completely. Used by
* unit tests.
*/
isDoneCallback: null,
/**
* The current state of the output.
*/
@ -119,6 +119,20 @@ NetworkPanel.prototype =
_contentType: null,
/**
* Function callback invoked whenever the panel content is updated. This is
* used only by tests.
*
* @private
* @type function
*/
_onUpdate: null,
get document() {
return this.iframe && this.iframe.contentWindow ?
this.iframe.contentWindow.document : null;
},
/**
* Small helper function that is nearly equal to l10n.getFormatStr
* except that it prefixes aName with "NetworkPanel.".
@ -151,9 +165,8 @@ NetworkPanel.prototype =
return this._contentType;
}
let entry = this.httpActivity.log.entries[0];
let request = entry.request;
let response = entry.response;
let request = this.httpActivity.request;
let response = this.httpActivity.response;
let contentType = "";
let types = response.content ?
@ -237,7 +250,7 @@ NetworkPanel.prototype =
*/
get _isResponseCached()
{
return this.httpActivity.log.entries[0].response.status == 304;
return this.httpActivity.response.status == 304;
},
/**
@ -248,7 +261,7 @@ NetworkPanel.prototype =
*/
get _isRequestBodyFormData()
{
let requestBody = this.httpActivity.log.entries[0].request.postData.text;
let requestBody = this.httpActivity.request.postData.text;
return this._fromDataRegExp.test(requestBody);
},
@ -342,9 +355,8 @@ NetworkPanel.prototype =
*/
_displayRequestHeader: function NP__displayRequestHeader()
{
let entry = this.httpActivity.log.entries[0];
let request = entry.request;
let requestTime = new Date(entry.startedDateTime);
let request = this.httpActivity.request;
let requestTime = new Date(this.httpActivity.startedDateTime);
this._appendTextNode("headUrl", request.url);
this._appendTextNode("headMethod", request.method);
@ -365,8 +377,9 @@ NetworkPanel.prototype =
*
* @returns void
*/
_displayRequestBody: function NP__displayRequestBody() {
let postData = this.httpActivity.log.entries[0].request.postData;
_displayRequestBody: function NP__displayRequestBody()
{
let postData = this.httpActivity.request.postData;
this._displayNode("requestBody");
this._appendTextNode("requestBodyContent", postData.text);
},
@ -377,8 +390,9 @@ NetworkPanel.prototype =
*
* @returns void
*/
_displayRequestForm: function NP__processRequestForm() {
let postData = this.httpActivity.log.entries[0].request.postData.text;
_displayRequestForm: function NP__processRequestForm()
{
let postData = this.httpActivity.request.postData.text;
let requestBodyLines = postData.split("\n");
let formData = requestBodyLines[requestBodyLines.length - 1].
replace(/\+/g, " ").split("&");
@ -418,9 +432,8 @@ NetworkPanel.prototype =
*/
_displayResponseHeader: function NP__displayResponseHeader()
{
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let response = entry.response;
let timing = this.httpActivity.timings;
let response = this.httpActivity.response;
this._appendTextNode("headStatus",
[response.httpVersion, response.status,
@ -454,16 +467,16 @@ NetworkPanel.prototype =
_displayResponseImage: function NP__displayResponseImage()
{
let self = this;
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let request = entry.request;
let timing = this.httpActivity.timings;
let request = this.httpActivity.request;
let cached = "";
if (this._isResponseCached) {
cached = "Cached";
}
let imageNode = this.document.getElementById("responseImage" + cached +"Node");
let imageNode = this.document.getElementById("responseImage" +
cached + "Node");
imageNode.setAttribute("src", request.url);
// This function is called to set the imageInfo.
@ -499,9 +512,8 @@ NetworkPanel.prototype =
*/
_displayResponseBody: function NP__displayResponseBody()
{
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let response = entry.response;
let timing = this.httpActivity.timings;
let response = this.httpActivity.response;
let cached = this._isResponseCached ? "Cached" : "";
this._appendTextNode("responseBody" + cached + "Info",
@ -520,7 +532,7 @@ NetworkPanel.prototype =
*/
_displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType()
{
let timing = this.httpActivity.log.entries[0].timings;
let timing = this.httpActivity.timings;
this._displayNode("responseBodyUnknownType");
this._appendTextNode("responseBodyUnknownTypeInfo",
@ -538,7 +550,7 @@ NetworkPanel.prototype =
*/
_displayNoResponseBody: function NP_displayNoResponseBody()
{
let timing = this.httpActivity.log.entries[0].timings;
let timing = this.httpActivity.timings;
this._displayNode("responseNoBody");
this._appendTextNode("responseNoBodyInfo",
@ -554,15 +566,14 @@ NetworkPanel.prototype =
{
// After the iframe's contentWindow is ready, the document object is set.
// If the document object is not available yet nothing needs to be updated.
if (!this.document) {
if (!this.document || !this.document.getElementById("headUrl")) {
return;
}
let stages = this.httpActivity.meta.stages;
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let request = entry.request;
let response = entry.response;
let updates = this.httpActivity.updates;
let timing = this.httpActivity.timings;
let request = this.httpActivity.request;
let response = this.httpActivity.response;
switch (this._state) {
case this._INIT:
@ -572,7 +583,7 @@ NetworkPanel.prototype =
case this._DISPLAYED_REQUEST_HEADER:
// Process the request body if there is one.
if (!this.httpActivity.meta.discardRequestBody && request.postData) {
if (!this.httpActivity.discardRequestBody && request.postData.text) {
// Check if we send some form data. If so, display the form data special.
if (this._isRequestBodyFormData) {
this._displayRequestForm();
@ -585,9 +596,6 @@ NetworkPanel.prototype =
// FALL THROUGH
case this._DISPLAYED_REQUEST_BODY:
// There is always a response header. Therefore we can skip here if
// we don't have a response header yet and don't have to try updating
// anything else in the NetworkPanel.
if (!response.headers.length || !Object.keys(timing).length) {
break;
}
@ -596,13 +604,13 @@ NetworkPanel.prototype =
// FALL THROUGH
case this._DISPLAYED_RESPONSE_HEADER:
if (stages.indexOf("REQUEST_STOP") == -1 ||
stages.indexOf("TRANSACTION_CLOSE") == -1) {
if (updates.indexOf("responseContent") == -1 ||
updates.indexOf("eventTimings") == -1) {
break;
}
this._state = this._TRANSITION_CLOSED;
if (this.httpActivity.meta.discardResponseBody) {
if (this.httpActivity.discardResponseBody) {
break;
}
@ -618,9 +626,12 @@ NetworkPanel.prototype =
else if (response.content.text) {
this._displayResponseBody();
}
break;
}
if (this._onUpdate) {
this._onUpdate();
}
}
}

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

@ -13,7 +13,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
@ -27,7 +27,7 @@ var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
*/
var PropertyTreeView = function() {
this._rows = [];
this._objectCache = {};
this._objectActors = [];
};
PropertyTreeView.prototype = {
@ -44,10 +44,24 @@ PropertyTreeView.prototype = {
_treeBox: null,
/**
* Stores cached information about local objects being inspected.
* Track known object actor IDs. We clean these when the panel is
* destroyed/cleaned up.
*
* @private
* @type array
*/
_objectCache: null,
_objectActors: null,
/**
* Map fake object actors to their IDs. This is used when we inspect local
* objects.
* @private
* @type Object
*/
_localObjectActors: null,
_releaseObject: null,
_objectPropertiesProvider: null,
/**
* Use this setter to update the content of the tree.
@ -58,54 +72,47 @@ PropertyTreeView.prototype = {
* - object:
* This is the raw object you want to display. You can only provide
* this object if you want the property panel to work in sync mode.
* - remoteObject:
* - objectProperties:
* An array that holds information on the remote object being
* inspected. Each element in this array describes each property in the
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
* - rootCacheId:
* The cache ID where the objects referenced in remoteObject are found.
* - panelCacheId:
* The cache ID where any object retrieved by this property panel
* instance should be stored into.
* - remoteObjectProvider:
* remote object. See WebConsoleUtils.inspectObject() for details.
* - objectPropertiesProvider:
* A function that is invoked when a new object is needed. This is
* called when the user tries to expand an inspectable property. The
* callback must take four arguments:
* - fromCacheId:
* Tells from where to retrieve the object the user picked (from
* which cache ID).
* - objectId:
* The object ID the user wants.
* - panelCacheId:
* Tells in which cache ID to store the objects referenced by
* objectId so they can be retrieved later.
* - actorID:
* The object actor ID from which we request the properties.
* - callback:
* The callback function to be invoked when the remote object is
* received. This function takes one argument: the raw message
* received from the Web Console content script.
* received. This function takes one argument: the array of
* descriptors for each property in the object represented by the
* actor.
* - releaseObject:
* Function to invoke when an object actor should be released. The
* function must take one argument: the object actor ID.
*/
set data(aData) {
let oldLen = this._rows.length;
this._cleanup();
this.cleanup();
if (!aData) {
return;
}
if (aData.remoteObject) {
this._rootCacheId = aData.rootCacheId;
this._panelCacheId = aData.panelCacheId;
this._remoteObjectProvider = aData.remoteObjectProvider;
this._rows = [].concat(aData.remoteObject);
this._updateRemoteObject(this._rows, 0);
if (aData.objectPropertiesProvider) {
this._objectPropertiesProvider = aData.objectPropertiesProvider;
this._releaseObject = aData.releaseObject;
this._propertiesToRows(aData.objectProperties, 0);
this._rows = aData.objectProperties;
}
else if (aData.object) {
this._localObjectActors = Object.create(null);
this._rows = this._inspectObject(aData.object);
}
else {
throw new Error("First argument must have a .remoteObject or " +
"an .object property!");
throw new Error("First argument must have an objectActor or an " +
"object property!");
}
if (this._treeBox) {
@ -128,13 +135,22 @@ PropertyTreeView.prototype = {
* @param number aLevel
* The level you want to give to each property in the remote object.
*/
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
_propertiesToRows: function PTV__propertiesToRows(aObject, aLevel)
{
aObject.forEach(function(aElement) {
aElement.level = aLevel;
aElement.isOpened = false;
aElement.children = null;
});
aObject.forEach(function(aItem) {
aItem._level = aLevel;
aItem._open = false;
aItem._children = null;
if (this._releaseObject) {
["value", "get", "set"].forEach(function(aProp) {
let val = aItem[aProp];
if (val && val.actor) {
this._objectActors.push(val.actor);
}
}, this);
}
}, this);
},
/**
@ -143,42 +159,53 @@ PropertyTreeView.prototype = {
* @private
* @param object aObject
* The object you want to inspect.
* @return array
* The array of properties, each being described in a way that is
* usable by the tree view.
*/
_inspectObject: function PTV__inspectObject(aObject)
{
this._objectCache = {};
this._remoteObjectProvider = this._localObjectProvider.bind(this);
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
this._updateRemoteObject(children, 0);
this._objectPropertiesProvider = this._localPropertiesProvider.bind(this);
let children =
WebConsoleUtils.inspectObject(aObject, this._localObjectGrip.bind(this));
this._propertiesToRows(children, 0);
return children;
},
/**
* An object provider for when the user inspects local objects (not remote
* Make a local fake object actor for the given object.
*
* @private
* @param object aObject
* The object to make an actor for.
* @return object
* The fake actor grip that represents the given object.
*/
_localObjectGrip: function PTV__localObjectGrip(aObject)
{
let grip = WebConsoleUtils.getObjectGrip(aObject);
grip.actor = "obj" + gSequenceId();
this._localObjectActors[grip.actor] = aObject;
return grip;
},
/**
* A properties provider for when the user inspects local objects (not remote
* ones).
*
* @private
* @param string aFromCacheId
* The cache ID from where to retrieve the desired object.
* @param string aObjectId
* The ID of the object you want.
* @param string aDestCacheId
* The ID of the cache where to store any objects referenced by the
* desired object.
* @param string aActor
* The ID of the object actor you want.
* @param function aCallback
* The function you want to receive the object.
* The function you want to receive the list of properties.
*/
_localObjectProvider:
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
aCallback)
_localPropertiesProvider:
function PTV__localPropertiesProvider(aActor, aCallback)
{
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
this._objectCache);
aCallback({cacheId: aFromCacheId,
objectId: aObjectId,
object: object,
childrenCacheId: aDestCacheId || aFromCacheId,
});
let object = this._localObjectActors[aActor];
let properties =
WebConsoleUtils.inspectObject(object, this._localObjectGrip.bind(this));
aCallback(properties);
},
/** nsITreeView interface implementation **/
@ -187,18 +214,20 @@ PropertyTreeView.prototype = {
get rowCount() { return this._rows.length; },
setTree: function(treeBox) { this._treeBox = treeBox; },
getCellText: function(idx, column) {
getCellText: function PTV_getCellText(idx, column)
{
let row = this._rows[idx];
return row.name + ": " + row.value;
return row.name + ": " + WebConsoleUtils.getPropertyPanelValue(row);
},
getLevel: function(idx) {
return this._rows[idx].level;
return this._rows[idx]._level;
},
isContainer: function(idx) {
return !!this._rows[idx].inspectable;
return typeof this._rows[idx].value == "object" && this._rows[idx].value &&
this._rows[idx].value.inspectable;
},
isContainerOpen: function(idx) {
return this._rows[idx].isOpened;
return this._rows[idx]._open;
},
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
@ -221,22 +250,22 @@ PropertyTreeView.prototype = {
hasNextSibling: function(idx, after)
{
var thisLevel = this.getLevel(idx);
return this._rows.slice(after + 1).some(function (r) r.level == thisLevel);
let thisLevel = this.getLevel(idx);
return this._rows.slice(after + 1).some(function (r) r._level == thisLevel);
},
toggleOpenState: function(idx)
{
let item = this._rows[idx];
if (!item.inspectable) {
if (!this.isContainer(idx)) {
return;
}
if (item.isOpened) {
if (item._open) {
this._treeBox.beginUpdateBatch();
item.isOpened = false;
item._open = false;
var thisLevel = item.level;
var thisLevel = item._level;
var t = idx + 1, deleteCount = 0;
while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
deleteCount++;
@ -251,31 +280,27 @@ PropertyTreeView.prototype = {
}
else {
let levelUpdate = true;
let callback = function _onRemoteResponse(aResponse) {
let callback = function _onRemoteResponse(aProperties) {
this._treeBox.beginUpdateBatch();
item.isOpened = true;
if (levelUpdate) {
this._updateRemoteObject(aResponse.object, item.level + 1);
item.children = aResponse.object;
this._propertiesToRows(aProperties, item._level + 1);
item._children = aProperties;
}
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item._children));
this._treeBox.rowCountChanged(idx + 1, item.children.length);
this._treeBox.rowCountChanged(idx + 1, item._children.length);
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
item._open = true;
}.bind(this);
if (!item.children) {
let fromCacheId = item.level > 0 ? this._panelCacheId :
this._rootCacheId;
this._remoteObjectProvider(fromCacheId, item.objectId,
this._panelCacheId, callback);
if (!item._children) {
this._objectPropertiesProvider(item.value.actor, callback);
}
else {
levelUpdate = false;
callback({object: item.children});
callback(item._children);
}
}
},
@ -298,18 +323,23 @@ PropertyTreeView.prototype = {
drop: function(index, orientation, dataTransfer) { },
canDrop: function(index, orientation, dataTransfer) { return false; },
_cleanup: function PTV__cleanup()
/**
* Cleanup the property tree view.
*/
cleanup: function PTV_cleanup()
{
if (this._rows.length) {
// Reset the existing _rows children to the initial state.
this._updateRemoteObject(this._rows, 0);
this._rows = [];
if (this._releaseObject) {
this._objectActors.forEach(this._releaseObject);
delete this._objectPropertiesProvider;
delete this._releaseObject;
}
if (this._localObjectActors) {
delete this._localObjectActors;
delete this._objectPropertiesProvider;
}
delete this._objectCache;
delete this._rootCacheId;
delete this._panelCacheId;
delete this._remoteObjectProvider;
this._rows = [];
this._objectActors = [];
},
};
@ -459,3 +489,9 @@ PropertyPanel.prototype.destroy = function PP_destroy()
this.tree = null;
}
function gSequenceId()
{
return gSequenceId.n++;
}
gSequenceId.n = 0;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -8,7 +8,7 @@ function test()
{
waitForExplicitFinish();
addTab("data:text/html,test for bug 676722 - inspectable objects for window.console");
addTab("data:text/html;charset=utf8,test for bug 676722 - inspectable objects for window.console");
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);

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

@ -21,7 +21,7 @@ function test() {
browser.removeEventListener("DOMContentLoaded", testTimestamp, false);
const TEST_TIMESTAMP = 12345678;
let date = new Date(TEST_TIMESTAMP);
let localizedString = WebConsoleUtils.l10n.timestampString(TEST_TIMESTAMP);
let localizedString = WCU_l10n.timestampString(TEST_TIMESTAMP);
isnot(localizedString.indexOf(date.getHours()), -1, "the localized " +
"timestamp contains the hours");
isnot(localizedString.indexOf(date.getMinutes()), -1, "the localized " +

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

@ -81,7 +81,7 @@ function testContextMenuCopy() {
}
function getExpectedClipboardText(aItem) {
return "[" + WebConsoleUtils.l10n.timestampString(aItem.timestamp) + "] " +
return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
aItem.clipboardText;
}

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

@ -37,14 +37,14 @@ function test() {
let uri = Services.io.newFileURI(dir);
addTab(uri.spec);
addTab("data:text/html;charset=utf8,<p>test file URI");
browser.addEventListener("load", function tabLoad() {
browser.removeEventListener("load", tabLoad, true);
openConsole(null, function(aHud) {
hud = aHud;
hud.jsterm.clearOutput();
browser.addEventListener("load", tabReload, true);
content.location.reload();
content.location = uri.spec;
});
}, true);
}

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

@ -14,25 +14,35 @@ let tab1, tab2, win1, win2;
let noErrors = true;
function tab1Loaded(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.removeEventListener(aEvent.type, tab1Loaded, true);
win2 = OpenBrowserWindow();
win2.addEventListener("load", win2Loaded, true);
}
function win2Loaded(aEvent) {
win2.removeEventListener(aEvent.type, arguments.callee, true);
win2.removeEventListener(aEvent.type, win2Loaded, true);
tab2 = win2.gBrowser.addTab();
tab2 = win2.gBrowser.addTab(TEST_URI);
win2.gBrowser.selectedTab = tab2;
tab2.linkedBrowser.addEventListener("load", tab2Loaded, true);
tab2.linkedBrowser.contentWindow.location = TEST_URI;
}
function tab2Loaded(aEvent) {
tab2.linkedBrowser.removeEventListener(aEvent.type, arguments.callee, true);
tab2.linkedBrowser.removeEventListener(aEvent.type, tab2Loaded, true);
waitForFocus(function() {
let consolesOpened = 0;
function onWebConsoleOpen() {
consolesOpened++;
if (consolesOpened == 2) {
Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
executeSoon(closeConsoles);
}
}
Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
function openConsoles() {
try {
HUDService.activateHUDForContext(tab1);
}
@ -48,6 +58,20 @@ function tab2Loaded(aEvent) {
ok(false, "HUDService.activateHUDForContext(tab2) exception: " + ex);
noErrors = false;
}
}
let consolesClosed = 0;
function onWebConsoleClose()
{
consolesClosed++;
if (consolesClosed == 2) {
Services.obs.removeObserver(onWebConsoleClose, "web-console-destroyed");
executeSoon(testEnd);
}
}
function closeConsoles() {
Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
try {
HUDService.deactivateHUDForContext(tab1);
@ -64,20 +88,26 @@ function tab2Loaded(aEvent) {
ok(false, "HUDService.deactivateHUDForContext(tab2) exception: " + ex);
noErrors = false;
}
}
if (noErrors) {
ok(true, "there were no errors");
}
function testEnd() {
ok(noErrors, "there were no errors");
win2.gBrowser.removeTab(tab2);
Array.forEach(win1.gBrowser.tabs, function(aTab) {
win1.gBrowser.removeTab(aTab);
});
Array.forEach(win2.gBrowser.tabs, function(aTab) {
win2.gBrowser.removeTab(aTab);
});
executeSoon(function() {
win2.close();
tab1 = tab2 = win1 = win2 = null;
finishTest();
});
}
}, tab2.linkedBrowser.contentWindow);
waitForFocus(openConsoles, tab2.linkedBrowser.contentWindow);
}
function test() {

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

@ -10,10 +10,12 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
function performTest(lastFinishedRequest)
function performTest(lastFinishedRequest, aConsole)
{
ok(lastFinishedRequest, "page load was logged");
let headers = null;
function readHeader(aName)
{
for (let header of headers) {
@ -24,13 +26,16 @@ function performTest(lastFinishedRequest)
return null;
}
let headers = lastFinishedRequest.log.entries[0].response.headers;
ok(headers, "we have the response headers");
ok(!readHeader("Content-Type"), "we do not have the Content-Type header");
isnot(readHeader("Content-Length"), 60, "Content-Length != 60");
aConsole.webConsoleClient.getResponseHeaders(lastFinishedRequest.actor,
function (aResponse) {
headers = aResponse.headers;
ok(headers, "we have the response headers");
ok(!readHeader("Content-Type"), "we do not have the Content-Type header");
isnot(readHeader("Content-Length"), 60, "Content-Length != 60");
executeSoon(finishTest);
});
HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
}
function test()

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

@ -10,39 +10,49 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
function performTest(lastFinishedRequest)
function performTest(lastFinishedRequest, aConsole)
{
ok(lastFinishedRequest, "charset test page was loaded and logged");
let body = lastFinishedRequest.log.entries[0].response.content.text;
ok(body, "we have the response body");
aConsole.webConsoleClient.getResponseContent(lastFinishedRequest.actor,
function (aResponse) {
ok(!aResponse.contentDiscarded, "response body was not discarded");
let chars = "\u7684\u95ee\u5019!"; // 的问候!
isnot(body.indexOf("<p>" + chars + "</p>"), -1,
"found the chinese simplified string");
let body = aResponse.content.text;
ok(body, "we have the response body");
let chars = "\u7684\u95ee\u5019!"; // 的问候!
isnot(body.indexOf("<p>" + chars + "</p>"), -1,
"found the chinese simplified string");
executeSoon(finishTest);
});
HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
}
function test()
{
addTab("data:text/html;charset=utf-8,Web Console - bug 600183 test");
let initialLoad = true;
browser.addEventListener("load", function onLoad() {
if (initialLoad) {
openConsole(null, function(hud) {
browser.removeEventListener("load", onLoad, true);
hud.ui.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = performTest;
openConsole(null, function(hud) {
hud.ui.saveRequestAndResponseBodies = true;
content.location = TEST_URI;
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: function()
{
HUDService.lastFinishedRequestCallback = performTest;
content.location = TEST_URI;
},
failureFn: finishTest,
});
initialLoad = false;
} else {
browser.removeEventListener("load", onLoad, true);
}
});
}, true);
}

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

@ -16,14 +16,14 @@ const MINIMUM_CONSOLE_HEIGHT = 150;
const MINIMUM_PAGE_HEIGHT = 50;
const HEIGHT_PREF = "devtools.hud.height";
let hud, newHeight, height, innerHeight;
let hud, newHeight, height, innerHeight, testDriver;
function performTests(aWebConsole)
function testGen()
{
hud = aWebConsole.iframe;
height = parseInt(hud.style.height);
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -31,6 +31,7 @@ function performTests(aWebConsole)
setHeight(Math.ceil(innerHeight * 0.5));
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -38,6 +39,7 @@ function performTests(aWebConsole)
setHeight(MINIMUM_CONSOLE_HEIGHT - 1);
toggleConsole();
yield;
is(newHeight, MINIMUM_CONSOLE_HEIGHT, "minimum console height is respected");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -45,6 +47,7 @@ function performTests(aWebConsole)
setHeight(innerHeight - MINIMUM_PAGE_HEIGHT + 1);
toggleConsole();
yield;
is(newHeight, innerHeight - MINIMUM_PAGE_HEIGHT,
"minimum page height is respected");
@ -54,6 +57,7 @@ function performTests(aWebConsole)
setHeight(Math.ceil(innerHeight * 0.6));
Services.prefs.setIntPref(HEIGHT_PREF, -1);
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), -1, "pref is not updated");
@ -62,17 +66,23 @@ function performTests(aWebConsole)
HUDService.lastConsoleHeight = 0;
Services.prefs.setIntPref(HEIGHT_PREF, 0);
hud = testDriver = null;
executeSoon(finishTest);
yield;
}
function toggleConsole()
{
closeConsole();
openConsole();
closeConsole(null, function() {
openConsole(null, function() {
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId].iframe;
newHeight = parseInt(hud.style.height);
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId].iframe;
newHeight = parseInt(hud.style.height);
testDriver.next();
});
});
}
function setHeight(aHeight)
@ -87,7 +97,11 @@ function test()
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
innerHeight = content.innerHeight;
openConsole(null, performTests);
openConsole(null, function(aHud) {
hud = aHud.iframe;
testDriver = testGen();
testDriver.next();
});
}, true);
}

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

@ -86,8 +86,17 @@ function onpopupshown2(aEvent)
});
}, false);
executeSoon(function() {
menupopups[1].hidePopup();
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return huds[1].ui.saveRequestAndResponseBodies;
},
successFn: function()
{
menupopups[1].hidePopup();
},
failureFn: finishTest,
});
}
@ -147,8 +156,17 @@ function onpopupshown1(aEvent)
}, tabs[runCount*2 + 1].linkedBrowser.contentWindow);
}, false);
executeSoon(function() {
menupopups[0].hidePopup();
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return huds[0].ui.saveRequestAndResponseBodies;
},
successFn: function()
{
menupopups[0].hidePopup();
},
failureFn: finishTest,
});
}

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

@ -80,6 +80,6 @@ function performTest(HUD) {
}
function getExpectedClipboardText(aItem) {
return "[" + WebConsoleUtils.l10n.timestampString(aItem.timestamp) + "] " +
return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
aItem.clipboardText;
}

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

@ -58,7 +58,7 @@ function getExpectedClipboardText(aItemCount) {
for (let i = 0; i < aItemCount; i++) {
let item = outputNode.getItemAtIndex(i);
expectedClipboardText.push("[" +
WebConsoleUtils.l10n.timestampString(item.timestamp) + "] " +
WCU_l10n.timestampString(item.timestamp) + "] " +
item.clipboardText);
}
return expectedClipboardText.join("\n");

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

@ -10,20 +10,86 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs";
let lastFinishedRequests = {};
let webConsoleClient;
function requestDoneCallback(aHttpRequest)
function requestDoneCallback(aHttpRequest )
{
let status = aHttpRequest.log.entries[0].response.status;
let status = aHttpRequest.response.status;
lastFinishedRequests[status] = aHttpRequest;
}
function performTest(aEvent)
function consoleOpened(hud)
{
webConsoleClient = hud.ui.webConsoleClient;
hud.ui.saveRequestAndResponseBodies = true;
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: function()
{
HUDService.lastFinishedRequestCallback = requestDoneCallback;
waitForSuccess(waitForResponses);
content.location = TEST_URI;
},
failureFn: finishTest,
});
let waitForResponses = {
name: "301 and 404 responses",
validatorFn: function()
{
return "301" in lastFinishedRequests &&
"404" in lastFinishedRequests;
},
successFn: getHeaders,
failureFn: finishTest,
};
}
function getHeaders()
{
HUDService.lastFinishedRequestCallback = null;
ok("301" in lastFinishedRequests, "request 1: 301 Moved Permanently");
ok("404" in lastFinishedRequests, "request 2: 404 Not found");
webConsoleClient.getResponseHeaders(lastFinishedRequests["301"].actor,
function (aResponse) {
lastFinishedRequests["301"].response.headers = aResponse.headers;
webConsoleClient.getResponseHeaders(lastFinishedRequests["404"].actor,
function (aResponse) {
lastFinishedRequests["404"].response.headers = aResponse.headers;
executeSoon(getContent);
});
});
}
function getContent()
{
webConsoleClient.getResponseContent(lastFinishedRequests["301"].actor,
function (aResponse) {
lastFinishedRequests["301"].response.content = aResponse.content;
lastFinishedRequests["301"].discardResponseBody = aResponse.contentDiscarded;
webConsoleClient.getResponseContent(lastFinishedRequests["404"].actor,
function (aResponse) {
lastFinishedRequests["404"].response.content = aResponse.content;
lastFinishedRequests["404"].discardResponseBody =
aResponse.contentDiscarded;
webConsoleClient = null;
executeSoon(performTest);
});
});
}
function performTest()
{
function readHeader(aName)
{
for (let header of headers) {
@ -34,7 +100,7 @@ function performTest(aEvent)
return null;
}
let headers = lastFinishedRequests["301"].log.entries[0].response.headers;
let headers = lastFinishedRequests["301"].response.headers;
is(readHeader("Content-Type"), "text/html",
"we do have the Content-Type header");
is(readHeader("Content-Length"), 71, "Content-Length is correct");
@ -42,14 +108,17 @@ function performTest(aEvent)
"Content-Length is correct");
is(readHeader("x-foobar-bug630733"), "bazbaz",
"X-Foobar-bug630733 is correct");
let body = lastFinishedRequests["301"].log.entries[0].response.content;
ok(!body.text, "body discarded for request 1");
headers = lastFinishedRequests["404"].log.entries[0].response.headers;
let body = lastFinishedRequests["301"].response.content;
ok(!body.text, "body discarded for request 1");
ok(lastFinishedRequests["301"].discardResponseBody,
"body discarded for request 1 (confirmed)");
headers = lastFinishedRequests["404"].response.headers;
ok(!readHeader("Location"), "no Location header");
ok(!readHeader("x-foobar-bug630733"), "no X-Foobar-bug630733 header");
body = lastFinishedRequests["404"].log.entries[0].response.content.text;
body = lastFinishedRequests["404"].response.content.text;
isnot(body.indexOf("404"), -1,
"body is correct for request 2");
@ -61,19 +130,8 @@ function test()
{
addTab("data:text/html;charset=utf-8,<p>Web Console test for bug 630733");
browser.addEventListener("load", function onLoad1(aEvent) {
browser.removeEventListener(aEvent.type, onLoad1, true);
openConsole(null, function(hud) {
hud.ui.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = requestDoneCallback;
browser.addEventListener("load", function onLoad2(aEvent) {
browser.removeEventListener(aEvent.type, onLoad2, true);
executeSoon(performTest);
}, true);
content.location = TEST_URI;
});
browser.addEventListener("load", function onLoad(aEvent) {
browser.removeEventListener(aEvent.type, onLoad, true);
openConsole(null, consoleOpened);
}, true);
}

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

@ -15,7 +15,7 @@ function test() {
function consoleOpened(HUD) {
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;
@ -117,7 +117,7 @@ function testPropertyPanel(aPanel) {
ok(find("iter1: Iterator", false),
"iter1 is correctly displayed in the Property Panel");
ok(find("iter2: Iterator", false),
ok(find("iter2: Object", false),
"iter2 is correctly displayed in the Property Panel");
executeSoon(finishTest);

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

@ -25,7 +25,7 @@ function test()
hud = aHud;
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest.log.entries[0];
lastRequest = aRequest;
if (requestCallback) {
requestCallback();
}

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

@ -23,7 +23,7 @@ function consoleOpened(aHud) {
let completeNode = jsterm.completeNode;
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
tmp = null;
@ -38,7 +38,8 @@ function consoleOpened(aHud) {
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
// constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
// toLocaleString toSource toString unwatch valueOf watch.
let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
let props = WCU.inspectObject(content.wrappedJSObject.document.body,
function() { });
is(popup.itemCount, 14 + props.length, "popup.itemCount is correct");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);

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

@ -41,7 +41,7 @@ function testConsoleDir(outputNode) {
if (text == "querySelectorAll: function querySelectorAll()") {
foundQSA = true;
}
else if (text == "location: Object") {
else if (text == "location: Location") {
foundLocation = true;
}
else if (text == "write: function write()") {

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

@ -13,7 +13,7 @@
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
function test() {
addTab("data:text/html,Web Console basic network logging test");
addTab("data:text/html;charset=utf8,Web Console mixed content test");
browser.addEventListener("load", onLoad, true);
}
@ -68,8 +68,9 @@ function testClickOpenNewTab(warningNode) {
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == "https://developer.mozilla.org/en/Security/MixedContent");
linkOpened = true;
if (aLink == "https://developer.mozilla.org/en/Security/MixedContent") {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},

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

@ -6,7 +6,7 @@
// Tests that code completion works properly.
function test() {
addTab("about:addons");
addTab("about:credits");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testChrome);
@ -15,7 +15,7 @@ function test() {
function testChrome(hud) {
ok(hud, "we have a console");
ok(hud.iframe, "we have the console iframe");
let jsterm = hud.jsterm;

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

@ -52,7 +52,7 @@ function testClipboard() {
for (let i = 0; i < outputNode.itemCount; i++) {
let item = outputNode.getItemAtIndex(i);
clipboardTexts.push("[" +
WebConsoleUtils.l10n.timestampString(item.timestamp) +
WCU_l10n.timestampString(item.timestamp) +
"] " + item.clipboardText);
}

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

@ -111,10 +111,10 @@ function testJSTerm(hud)
let foundTab = null;
waitForSuccess({
name: "help tab opened",
name: "help tabs opened",
validatorFn: function()
{
let newTabOpen = gBrowser.tabs.length == tabs + 1;
let newTabOpen = gBrowser.tabs.length == tabs + 3;
if (!newTabOpen) {
return false;
}
@ -124,7 +124,9 @@ function testJSTerm(hud)
},
successFn: function()
{
gBrowser.removeTab(foundTab);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
nextTest();
},
failureFn: nextTest,
@ -176,7 +178,7 @@ function testJSTerm(hud)
jsterm.clearOutput();
jsterm.execute("pprint(print)");
checkResult(function(nodes) {
return nodes[0].textContent.indexOf("aJSTerm.") > -1;
return nodes[0].textContent.indexOf("aOwner.helperResult") > -1;
}, "pprint(function) shows source", 1);
yield;

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

@ -21,7 +21,6 @@ const TEST_DATA_JSON_CONTENT =
let lastRequest = null;
let requestCallback = null;
let lastActivity = null;
function test()
{
@ -33,19 +32,34 @@ function test()
openConsole(null, function(aHud) {
hud = aHud;
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest.log.entries[0];
lastActivity = aRequest;
if (requestCallback) {
requestCallback();
}
};
HUDService.lastFinishedRequestCallback = requestCallbackWrapper;
executeSoon(testPageLoad);
});
}, true);
}
function requestCallbackWrapper(aRequest)
{
lastRequest = aRequest;
hud.ui.webConsoleClient.getResponseContent(lastRequest.actor,
function(aResponse) {
lastRequest.response.content = aResponse.content;
lastRequest.discardResponseBody = aResponse.contentDiscarded;
hud.ui.webConsoleClient.getRequestPostData(lastRequest.actor,
function(aResponse) {
lastRequest.request.postData = aResponse.postData;
lastRequest.discardRequestBody = aResponse.postDataDiscarded;
if (requestCallback) {
requestCallback();
}
});
});
}
function testPageLoad()
{
requestCallback = function() {
@ -55,8 +69,10 @@ function testPageLoad()
is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
"Logged network entry is page load");
is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was stored");
ok(!lastRequest.request.postData.text, "No request body was stored");
ok(lastRequest.discardRequestBody, "Request body was discarded");
ok(!lastRequest.response.content.text, "No response body was stored");
ok(lastRequest.discardResponseBody, "Response body was discarded");
lastRequest = null;
requestCallback = null;
@ -67,14 +83,29 @@ function testPageLoad()
}
function testPageLoadBody()
{
// Turn on logging of request bodies and check again.
hud.ui.saveRequestAndResponseBodies = true;
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: testPageLoadBodyAfterSettingUpdate,
failureFn: finishTest,
});
}
function testPageLoadBodyAfterSettingUpdate()
{
let loaded = false;
let requestCallbackInvoked = false;
// Turn on logging of request bodies and check again.
hud.ui.saveRequestAndResponseBodies = true;
requestCallback = function() {
ok(lastRequest, "Page load was logged again");
ok(!lastRequest.discardResponseBody, "Response body was not discarded");
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
"Response body's beginning is okay");
@ -104,7 +135,8 @@ function testXhrGet()
requestCallback = function() {
ok(lastRequest, "testXhrGet() was logged");
is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was sent");
ok(!lastRequest.request.postData.text, "No request body was sent");
ok(!lastRequest.discardRequestBody, "Request body was not discarded");
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
"Response is correct");
@ -165,19 +197,18 @@ function testNetworkPanel()
{
// Open the NetworkPanel. The functionality of the NetworkPanel is tested
// within separate test files.
let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastActivity);
is(networkPanel, hud.ui.filterBox._netPanel,
"Network panel stored on anchor node");
let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastRequest);
networkPanel.panel.addEventListener("load", function onLoad(aEvent) {
networkPanel.panel.removeEventListener(aEvent.type, onLoad, true);
networkPanel.panel.addEventListener("popupshown", function onPopupShown() {
networkPanel.panel.removeEventListener("popupshown", onPopupShown, true);
is(hud.ui.filterBox._netPanel, networkPanel,
"Network panel stored on anchor node");
ok(true, "NetworkPanel was opened");
// All tests are done. Shutdown.
networkPanel.panel.hidePopup();
lastRequest = null;
lastActivity = null;
HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
}, true);

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

@ -64,48 +64,37 @@ function testGen() {
let hud = HUDService.getHudByWindow(content);
let filterBox = hud.ui.filterBox;
let tempScope = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tempScope);
let l10n = tempScope.WebConsoleUtils.l10n;
tempScope = null;
let httpActivity = {
meta: {
stages: [],
discardRequestBody: true,
discardResponseBody: true,
updates: [],
discardRequestBody: true,
discardResponseBody: true,
startedDateTime: (new Date()).toISOString(),
request: {
url: "http://www.testpage.com",
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
],
},
log: {
entries: [{
startedDateTime: (new Date()).toISOString(),
request: {
url: "http://www.testpage.com",
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
],
},
response: {
headers: [],
content: {},
},
timings: {},
}],
response: {
headers: [],
content: {},
},
timings: {},
};
let entry = httpActivity.log.entries[0];
let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel,
"Network panel stored on the anchor object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -128,8 +117,8 @@ function testGen() {
// Test request body.
info("test 2: request body");
httpActivity.meta.discardRequestBody = false;
entry.request.postData = { text: "hello world" };
httpActivity.discardRequestBody = false;
httpActivity.request.postData = { text: "hello world" };
networkPanel.update();
checkIsVisible(networkPanel, {
@ -146,12 +135,12 @@ function testGen() {
// Test response header.
info("test 3: response header");
entry.timings.wait = 10;
entry.response.httpVersion = "HTTP/3.14";
entry.response.status = 999;
entry.response.statusText = "earthquake win";
entry.response.content.mimeType = "text/html";
entry.response.headers.push(
httpActivity.timings.wait = 10;
httpActivity.response.httpVersion = "HTTP/3.14";
httpActivity.response.status = 999;
httpActivity.response.statusText = "earthquake win";
httpActivity.response.content.mimeType = "text/html";
httpActivity.response.headers.push(
{ name: "Content-Type", value: "text/html" },
{ name: "leaveHouses", value: "true" }
);
@ -175,8 +164,8 @@ function testGen() {
info("test 4");
httpActivity.meta.discardResponseBody = false;
entry.timings.receive = 2;
httpActivity.discardResponseBody = false;
httpActivity.timings.receive = 2;
networkPanel.update();
checkIsVisible(networkPanel, {
@ -192,7 +181,7 @@ function testGen() {
info("test 5");
httpActivity.meta.stages.push("REQUEST_STOP", "TRANSACTION_CLOSE");
httpActivity.updates.push("responseContent", "eventTimings");
networkPanel.update();
checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms");
@ -210,20 +199,22 @@ function testGen() {
// Second run: Test for cookies and response body.
info("test 6: cookies and response body");
entry.request.cookies.push(
httpActivity.request.cookies.push(
{ name: "foo", value: "bar" },
{ name: "hello", value: "world" }
);
entry.response.content.text = "get out here";
httpActivity.response.content.text = "get out here";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel,
"Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -247,15 +238,17 @@ function testGen() {
// Check image request.
info("test 7: image request");
entry.response.headers[1].value = "image/png";
entry.response.content.mimeType = "image/png";
entry.request.url = TEST_IMG;
httpActivity.response.headers[1].value = "image/png";
httpActivity.response.content.mimeType = "image/png";
httpActivity.request.url = TEST_IMG;
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -296,15 +289,17 @@ function testGen() {
// Check cached image request.
info("test 8: cached image request");
entry.response.httpVersion = "HTTP/1.1";
entry.response.status = 304;
entry.response.statusText = "Not Modified";
httpActivity.response.httpVersion = "HTTP/1.1";
httpActivity.response.status = 304;
httpActivity.response.statusText = "Not Modified";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -326,17 +321,19 @@ function testGen() {
// Test sent form data.
info("test 9: sent form data");
entry.request.postData.text = [
httpActivity.request.postData.text = [
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: 59",
"name=rob&age=20"
].join("\n");
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -357,13 +354,15 @@ function testGen() {
// Test no space after Content-Type:
info("test 10: no space after Content-Type header in post data");
entry.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n";
httpActivity.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -384,16 +383,18 @@ function testGen() {
info("test 11: cached data");
entry.request.url = TEST_ENCODING_ISO_8859_1;
entry.response.headers[1].value = "application/json";
entry.response.content.mimeType = "application/json";
entry.response.content.text = "my cached data is here!";
httpActivity.request.url = TEST_ENCODING_ISO_8859_1;
httpActivity.response.headers[1].value = "application/json";
httpActivity.response.content.mimeType = "application/json";
httpActivity.response.content.text = "my cached data is here!";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -417,14 +418,16 @@ function testGen() {
// Test a response with a content type that can't be displayed in the
// NetworkPanel.
info("test 12: unknown content type");
entry.response.headers[1].value = "application/x-shockwave-flash";
entry.response.content.mimeType = "application/x-shockwave-flash";
httpActivity.response.headers[1].value = "application/x-shockwave-flash";
httpActivity.response.content.mimeType = "application/x-shockwave-flash";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
networkPanel._onUpdate = function() {
networkPanel._onUpdate = null;
executeSoon(function() {
testDriver.next();
});
};
yield;
@ -442,7 +445,7 @@ function testGen() {
});
let responseString =
l10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content",
WCU_l10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content",
["application/x-shockwave-flash"]);
checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString);
networkPanel.panel.hidePopup();

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

@ -17,7 +17,7 @@ function testPropertyProvider() {
browser.removeEventListener("load", testPropertyProvider, true);
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;

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

@ -6,8 +6,10 @@
let tempScope = {};
Cu.import("resource:///modules/HUDService.jsm", tempScope);
let HUDService = tempScope.HUDService;
Cu.import("resource:///modules/WebConsoleUtils.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
let WebConsoleUtils = tempScope.WebConsoleUtils;
const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
function log(aMsg)
{
@ -252,7 +254,7 @@ function tearDown()
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
tab = browser = hudId = hud = filterBox = outputNode = cs = null;
WCU_l10n = tab = browser = hudId = hud = filterBox = outputNode = cs = null;
}
registerCleanupFunction(tearDown);

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

@ -1,5 +1,6 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
<meta charset="utf8">
<title>Mixed Content test - http on https</title>
<script src="testscript.js"></script>
<!--

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -266,6 +266,9 @@
@BINPATH@/components/necko.xpt
@BINPATH@/components/loginmgr.xpt
@BINPATH@/components/parentalcontrols.xpt
#ifdef MOZ_WEBRTC
@BINPATH@/components/peerconnection.xpt
#endif
@BINPATH@/components/places.xpt
@BINPATH@/components/plugin.xpt
@BINPATH@/components/pref.xpt
@ -490,6 +493,11 @@
@BINPATH@/components/TCPSocketParentIntermediary.js
@BINPATH@/components/TCPSocket.manifest
#ifdef MOZ_WEBRTC
@BINPATH@/components/PeerConnection.js
@BINPATH@/components/PeerConnection.manifest
#endif
#ifdef ENABLE_MARIONETTE
@BINPATH@/chrome/marionette@JAREXT@
@BINPATH@/chrome/marionette.manifest

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

@ -48,8 +48,6 @@ addonsInstalled=#1 has been installed successfully.;#2 add-ons have been install
addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
addonInstallRestartButton=Restart Now
addonInstallRestartButton.accesskey=R
addonInstallManage=Open Add-ons Manager
addonInstallManage.accesskey=O
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
# #1 is the add-on name, #2 is the host name, #3 is the application name
@ -120,7 +118,7 @@ carbonFailurePluginsMessage.message=This page asks to use a plugin that can only
carbonFailurePluginsMessage.restartButton.label=Restart in 32-bit mode
carbonFailurePluginsMessage.restartButton.accesskey=R
activatePluginsMessage.message=Would you like to activate the plugins on this page?
activatePluginsMessage.label=Activate All Plugins
activateAllPluginsMessage.label=Activate All Plugins
activatePluginsMessage.accesskey=A
activatePluginsMessage.always=Always activate plugins for this site
activatePluginsMessage.always.accesskey=c

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

@ -817,11 +817,10 @@ jsbIndentCharDesc=The chars used to indent each line
# does.
jsbIndentCharManual=The chars used to indent each line. The possible choices are space or tab.
# LOCALIZATION NOTE (jsbPreserveNewlinesDesc) A very short description of the
# 'jsb <jsbPreserveNewlines>' parameter. This string is designed to be shown
# in a menu alongside the command name, which is why it should be as short as
# possible.
jsbPreserveNewlinesDesc=Keep existing line breaks?
# the 'jsb <doNotPreserveNewlines>' parameter. This string is designed to be
# shown in a menu alongside the command name, which is why it should be as short
# as possible.
jsbDoNotPreserveNewlinesDesc=Do not preserve line breaks
# LOCALIZATION NOTE (jsbPreserveNewlinesManual) A fuller description of the
# 'jsb <jsbPreserveNewlines>' parameter, displayed when the user asks for help
@ -854,23 +853,18 @@ jsbJslintHappyManual=When set to true, jslint-stricter mode is enforced
# 'jsb <braceStyle>' parameter. This string is designed to be shown
# in a menu alongside the command name, which is why it should be as short as
# possible.
jsbBraceStyleDesc=Collapse, expand, end-expand, expand-strict
jsbBraceStyleDesc=Select the coding style of braces
# LOCALIZATION NOTE (jsbBraceStyleManual) A fuller description of the
# 'jsb <braceStyle>' parameter, displayed when the user asks for help
# on what it does.
jsbBraceStyleManual=The coding style of braces. Either collapse, expand, end-expand or expand-strict
jsbBraceStyleManual=<p class="nowrap">The coding style of braces. Select from one of the following:</p><ul><li>collapse<br/><pre>if (x == 1) {\n ...\n} else {\n ...\n}</pre></li><li>expand<br/><pre>if (x == 1)\n{\n ...\n}\nelse\n{\n ...\n}</pre></li><li>end-expand<br/><pre>if (x == 1) {\n ...\n}\nelse {\n ...\n}</pre></li><li>expand-strict<br/><pre>if (x == 1)\n{\n return // This option can break scripts\n {\n a: 1\n };\n} else {\n ...\n}</pre></li></ul>
# LOCALIZATION NOTE (jsbSpaceBeforeConditionalDesc) A very short description of
# the 'jsb <spaceBeforeConditional>' parameter. This string is designed to be
# shown in a menu alongside the command name, which is why it should be as short
# as possible.
jsbSpaceBeforeConditionalDesc=Space before if statements?
# LOCALIZATION NOTE (jsbSpaceBeforeConditionalManual) A fuller description of
# the 'jsb <spaceBeforeConditional>' parameter, displayed when the user asks for
# help on what it does.
jsbSpaceBeforeConditionalManual=Should a space be added before conditional statements?
# LOCALIZATION NOTE (jsbNoSpaceBeforeConditionalDesc) A very short description
# of the 'jsb <noSpaceBeforeConditional>' parameter. This string is designed to
# be shown in a menu alongside the command name, which is why it should be as
# short as possible.
jsbNoSpaceBeforeConditionalDesc=No space before conditional statements
# LOCALIZATION NOTE (jsbUnescapeStringsDesc) A very short description of the
# 'jsb <unescapeStrings>' parameter. This string is designed to be shown
@ -887,6 +881,10 @@ jsbUnescapeStringsManual=Should printable characters in strings encoded in \\xNN
# the jsb command.
jsbInvalidURL=Please enter a valid URL
# LOCALIZATION NOTE (jsbOptionsDesc) The title of a set of options to
# the 'jsb' command, displayed as a heading to the list of options.
jsbOptionsDesc=Options
# LOCALIZATION NOTE (calllogDesc) A very short description of the
# 'calllog' command. This string is designed to be shown in a menu
# alongside the command name, which is why it should be as short as possible.

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

@ -2444,6 +2444,12 @@ html|*#gcli-output-frame {
0 1px 0 hsla(210,16%,76%,.15);
}
.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
background-color: hsl(210,30%,85%);
color: hsl(210,11%,16%);
text-shadow: none;
}
.gclitoolbar-complete-node {
padding-left: 21px;
background-color: transparent;
@ -2835,17 +2841,11 @@ chatbox[minimized="true"] {
height: 20px;
}
panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="top"],
panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="bottom"] {
list-style-image: url("chrome://global/skin/icons/panelarrow-light-vertical.svg");
}
.click-to-play-plugins-notification-content {
margin: -10px;
}
.click-to-play-plugins-notification-icon-box {
background: hsla(0,0%,100%,.4);
-moz-border-end: 1px solid hsla(0,0%,100%,.2);
padding-top: 16px;
-moz-padding-start: 16px;

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

@ -63,10 +63,15 @@
.gcli-row-out h4,
.gcli-row-out h5,
.gcli-row-out th,
.gcli-row-out strong {
.gcli-row-out strong,
.gcli-row-out pre {
color: hsl(210,30%,95%);
}
.gcli-row-out pre {
font-size: 80%;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;

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

@ -1,402 +0,0 @@
/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2007, Parakey Inc.
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* * Neither the name of Parakey Inc. nor the names of its
* contributors may be used to endorse or promote products
* derived from this software without specific prior
* written permission of Parakey Inc.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Creator:
* Joe Hewitt
* Contributors
* John J. Barton (IBM Almaden)
* Jan Odvarko (Mozilla Corp.)
* Max Stepanov (Aptana Inc.)
* Rob Campbell (Mozilla Corp.)
* Hans Hillen (Paciello Group, Mozilla)
* Curtis Bartley (Mozilla Corp.)
* Mike Collins (IBM Almaden)
* Kevin Decker
* Mike Ratcliffe (Comartis AG)
* Hernan Rodríguez Colmeiro
* Austin Andrews
* Christoph Dorn
* Steven Roussey (AppCenter Inc, Network54)
*/
html {
background-color: -moz-dialog;
}
body {
margin: 0;
overflow: auto;
font-family: Lucida Grande, sans-serif;
font-size: 11px;
padding-top: 5px;
}
h1 {
font-size: 17px;
border-bottom: 1px solid threedlightshadow;
}
a {
color: #0000ff;
}
pre {
margin: 0;
font: inherit;
}
code {
display: block;
white-space: pre;
}
/* DOMPlate */
.objectLink-element,
.objectLink-textNode,
.objectLink-function,
.objectBox-stackTrace,
.objectLink-profile {
font-family: Menlo, Andale Mono, monospace;
}
.objectLink-textNode {
white-space: pre-wrap;
}
.objectLink-styleRule,
.objectLink-element,
.objectLink-textNode {
color: #000088;
}
.selectorTag,
.selectorId,
.selectorClass {
font-family: Menlo, Andale Mono, monospace;
font-weight: normal;
}
.selectorTag {
color: #0000FF;
}
.selectorId {
color: DarkBlue;
}
.selectorClass {
color: red;
}
.selectorHidden > .selectorTag {
color: #5F82D9;
}
.selectorHidden > .selectorId {
color: #888888;
}
.selectorHidden > .selectorClass {
color: #D86060;
}
.selectorValue {
font-family: Menlo, Andale Mono, monospace;
font-style: italic;
color: #555555;
}
.panelNode-html {
-moz-box-sizing: padding-box;
padding: 4px 0 0 2px;
}
.nodeBox {
position: relative;
font-family: Menlo, Andale Mono, monospace;
padding-left: 13px;
-moz-user-select: -moz-none;
}
.nodeBox.search-selection {
-moz-user-select: text;
}
.twisty {
position: absolute;
left: 0px;
padding: 8px;
}
.nodeChildBox {
margin-left: 12px;
display: none;
}
.nodeLabel,
.nodeCloseLabel {
margin: -2px 2px 0 2px;
border: 2px solid transparent;
border-radius: 3px;
padding: 0 2px;
color: #000088;
}
.nodeCloseLabel {
display: none;
}
.nodeTag {
cursor: pointer;
color: blue;
}
.nodeValue {
color: #FF0000;
font-weight: normal;
}
.nodeText,
.nodeComment {
margin: 0 2px;
vertical-align: top;
}
.nodeText {
color: #333333;
}
.docType {
position: absolute;
/* position DOCTYPE element above/outside the "nodeBox" that contains it */
/* Note: to be fixed in Bug #688439 */
top: -16px;
font-family: Menlo, Andale Mono, monospace;
padding-left: 8px;
color: #999;
white-space: nowrap;
font-style: italic;
}
.htmlNodeBox {
/* make room for DOCTYPE element to be rendered above/outside "nodeBox" */
/* Note: to be fixed in Bug #688439 */
margin-top: 16px;
}
.nodeWhiteSpace {
border: 1px solid LightGray;
white-space: pre; /* otherwise the border will be collapsed around zero pixels */
margin-left: 1px;
color: gray;
}
.nodeWhiteSpace_Space {
border: 1px solid #ddd;
}
.nodeTextEntity {
border: 1px solid gray;
white-space: pre; /* otherwise the border will be collapsed around zero pixels */
margin-left: 1px;
}
.nodeComment {
color: DarkGreen;
}
.nodeBox.highlightOpen > .nodeLabel {
background-color: #EEEEEE;
}
.nodeBox.highlightOpen > .nodeCloseLabel,
.nodeBox.highlightOpen > .nodeChildBox,
.nodeBox.open > .nodeCloseLabel,
.nodeBox.open > .nodeChildBox {
display: block;
}
.nodeBox.selected > .nodeLabel > .nodeLabelBox,
.nodeBox.selected > .nodeLabel {
border-color: Highlight;
background-color: Highlight;
color: HighlightText !important;
}
.nodeBox.selected > .nodeLabel > .nodeLabelBox,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeTag,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText {
color: inherit !important;
}
.nodeBox.highlighted > .nodeLabel {
border-color: Highlight !important;
background-color: cyan !important;
color: #000000 !important;
}
.nodeBox.highlighted > .nodeLabel > .nodeLabelBox,
.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeTag,
.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeText {
color: #000000 !important;
}
.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox,
.nodeBox.nodeHidden .nodeCloseLabel,
.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeText,
.nodeBox.nodeHidden .nodeText {
color: #888888;
}
.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeTag,
.nodeBox.nodeHidden .nodeCloseLabel > .nodeCloseLabelBox > .nodeTag {
color: #5F82D9;
}
.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue {
color: #D86060;
}
.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox,
.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeTag,
.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeText {
color: SkyBlue !important;
}
.nodeBox.mutated > .nodeLabel,
.nodeAttr.mutated,
.nodeValue.mutated,
.nodeText.mutated,
.nodeBox.mutated > .nodeText {
background-color: #EFFF79;
color: #FF0000 !important;
}
.nodeBox.selected.mutated > .nodeLabel,
.nodeBox.selected.mutated > .nodeLabel > .nodeLabelBox,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr.mutated > .nodeValue,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue.mutated,
.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText.mutated {
background-color: #EFFF79;
border-color: #EFFF79;
color: #FF0000 !important;
}
.logRow-dirxml {
padding-left: 0;
}
.soloElement > .nodeBox {
padding-left: 0;
}
.useA11y .nodeLabel.focused {
outline: 2px solid #FF9933;
-moz-outline-radius: 3px;
outline-offset: -2px;
}
.useA11y .nodeLabelBox:focus {
outline: none;
}
/* from panel.css */
/* HTML panel */
.nodeBox.selected > .nodeLabel > .nodeLabelBox,
.nodeBox.selected > .nodeLabel {
border-color: #3875d7;
background-color: #3875d7;
color: #FFFFFF !important;
}
.nodeBox.highlighted > .nodeLabel {
border-color: #3875d7 !important;
}
/************************************************************************************************/
/* Twisties */
.twisty
{
-moz-appearance: treetwisty;
}
.nodeBox.highlightOpen > .nodeLabel > .twisty,
.nodeBox.open > .nodeLabel > .twisty
{
-moz-appearance: treetwistyopen;
}
/************************************************************************************************/
/* HTML panel */
.nodeBox.selected > .nodeLabel > .nodeLabelBox,
.nodeBox.selected > .nodeLabel {
border-color: #3875d7;
background-color: #3875d7;
color: #FFFFFF !important;
}
.nodeBox.highlighted > .nodeLabel {
border-color: #3875d7 !important;
}
.editingAttributeValue {
background-color: #492;
}
#attribute-editor {
visibility: hidden;
position: absolute;
z-index: 5000;
background-color: #fff;
border: 1px solid #000;
}
#attribute-editor.editing {
visibility: visible;
}
#attribute-editor-input {
border: none;
padding: 2px 5px;
font-family: Menlo, Andale Mono, monospace;
font-size: 11px;
}

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

@ -110,7 +110,6 @@ browser.jar:
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css)
skin/classic/browser/devtools/htmlpanel.css (devtools/htmlpanel.css)
skin/classic/browser/devtools/markup-view.css (devtools/markup-view.css)
skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)

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

@ -3866,6 +3866,12 @@ html|*#gcli-output-frame {
0 1px 0 hsla(210,16%,76%,.15);
}
.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
background-color: hsl(210,30%,85%);
color: hsl(210,11%,16%);
text-shadow: none;
}
.gclitoolbar-complete-node {
padding-left: 21px;
background-color: transparent;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше