Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2012-10-20 18:02:38 -04:00
commit 4dfc29c3c1
32 changed files with 809 additions and 184 deletions

View File

@ -117,7 +117,7 @@ DeviceTabActor.prototype.grip = function DTA_grip() {
// Walk over tab actors added by extensions and add them to a new ActorPool.
let actorPool = new ActorPool(this.conn);
this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
if (!actorPool.isEmpty()) {
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);

View File

@ -152,6 +152,7 @@
<menupopup id="appmenu_webDeveloper_popup">
<menuitem id="appmenu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar"/>
<menuitem id="appmenu_webConsole" observes="devtoolsMenuBroadcaster_WebConsole"/>
<menuitem id="appmenu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
<menuitem id="appmenu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect"/>
<menuitem id="appmenu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
<menuitem id="appmenu_debugger" observes="devtoolsMenuBroadcaster_Debugger"/>

View File

@ -534,6 +534,7 @@
<menupopup id="menuWebDeveloperPopup">
<menuitem id="menu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar" accesskey="&devToolbarMenu.accesskey;"/>
<menuitem id="webConsole" observes="devtoolsMenuBroadcaster_WebConsole" accesskey="&webConsoleCmd.accesskey;"/>
<menuitem id="menu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
<menuitem id="menu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect" accesskey="&inspectMenu.accesskey;"/>
<menuitem id="menu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI" accesskey="&responsiveDesignTool.accesskey;"/>
<menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger" accesskey="&debuggerMenu.accesskey;"/>

View File

@ -91,6 +91,7 @@
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
<command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
<command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
<command id="Tools:RemoteWebConsole" oncommand="HUDConsoleUI.toggleRemoteHUD();" disabled="true" hidden="true"/>
<command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
<command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/>
<command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/>
@ -203,6 +204,10 @@
type="checkbox" autocheck="false"
key="key_webConsole"
command="Tools:WebConsole"/>
<broadcaster id="devtoolsMenuBroadcaster_RemoteWebConsole"
label="&remoteWebConsoleCmd.label;"
type="checkbox" autocheck="false"
command="Tools:RemoteWebConsole"/>
<broadcaster id="devtoolsMenuBroadcaster_Inspect"
label="&inspectMenu.label;"
type="checkbox" autocheck="false"

View File

@ -1446,6 +1446,10 @@ var gBrowserInit = {
let cmd = document.getElementById("Tools:RemoteDebugger");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
cmd = document.getElementById("Tools:RemoteWebConsole");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Chrome Debugger?

View File

@ -23,6 +23,7 @@ Cu.import("resource://gre/modules/Services.jsm");
*/
var ScratchpadManager = {
_nextUid: 1,
_scratchpads: [],
/**
@ -89,16 +90,20 @@ var ScratchpadManager = {
*/
openScratchpad: function SPM_openScratchpad(aState)
{
let params = null;
let params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
.createInstance(Ci.nsIDialogParamBlock);
params.SetNumberStrings(2);
params.SetString(0, JSON.stringify(this._nextUid++));
if (aState) {
if (typeof aState != 'object') {
return;
}
params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
.createInstance(Ci.nsIDialogParamBlock);
params.SetNumberStrings(1);
params.SetString(0, JSON.stringify(aState));
params.SetString(1, JSON.stringify(aState));
}
let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
SCRATCHPAD_WINDOW_FEATURES, params);
// Only add the shutdown observer if we've opened a scratchpad window.

View File

@ -41,6 +41,7 @@ const BUTTON_POSITION_REVERT=0;
* The scratchpad object handles the Scratchpad window functionality.
*/
var Scratchpad = {
_instanceId: null,
_initialWindowTitle: document.title,
/**
@ -204,6 +205,15 @@ var Scratchpad = {
*/
_contentSandbox: null,
/**
* Unique name for the current Scratchpad instance. Used to distinguish
* Scratchpad windows between each other. See bug 661762.
*/
get uniqueName()
{
return "Scratchpad/" + this._instanceId;
},
/**
* Get the Cu.Sandbox object for the active tab content window object. Note
* that the returned object is cached for later reuse. The cached object is
@ -225,7 +235,7 @@ var Scratchpad = {
this._previousLocation != this.gBrowser.contentWindow.location.href) {
let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
this._contentSandbox = new Cu.Sandbox(contentWindow,
{ sandboxPrototype: contentWindow, wantXrays: false,
{ sandboxPrototype: contentWindow, wantXrays: false,
sandboxName: 'scratchpad-content'});
this._contentSandbox.__SCRATCHPAD__ = this;
@ -260,7 +270,7 @@ var Scratchpad = {
if (!this._chromeSandbox ||
this.browserWindow != this._previousBrowserWindow) {
this._chromeSandbox = new Cu.Sandbox(this.browserWindow,
{ sandboxPrototype: this.browserWindow, wantXrays: false,
{ sandboxPrototype: this.browserWindow, wantXrays: false,
sandboxName: 'scratchpad-chrome'});
this._chromeSandbox.__SCRATCHPAD__ = this;
addDebuggerToGlobal(this._chromeSandbox);
@ -317,7 +327,7 @@ var Scratchpad = {
let error, result;
try {
result = Cu.evalInSandbox(aString, this.contentSandbox, "1.8",
"Scratchpad", 1);
this.uniqueName, 1);
}
catch (ex) {
error = ex;
@ -339,7 +349,7 @@ var Scratchpad = {
let error, result;
try {
result = Cu.evalInSandbox(aString, this.chromeSandbox, "1.8",
"Scratchpad", 1);
this.uniqueName, 1);
}
catch (ex) {
error = ex;
@ -676,8 +686,6 @@ var Scratchpad = {
}
if (shouldOpen) {
this._skipClosePrompt = true;
let file;
if (aFile) {
file = aFile;
@ -1087,6 +1095,7 @@ var Scratchpad = {
if (aEvent.target != document) {
return;
}
let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
if (chrome) {
let environmentMenu = document.getElementById("sp-environment-menu");
@ -1097,8 +1106,6 @@ var Scratchpad = {
errorConsoleCommand.removeAttribute("disabled");
}
let state = null;
let initialText = this.strings.formatStringFromName(
"scratchpadIntro1",
[LayoutHelpers.prettyKey(document.getElementById("sp-key-run")),
@ -1106,9 +1113,21 @@ var Scratchpad = {
LayoutHelpers.prettyKey(document.getElementById("sp-key-display"))],
3);
if ("arguments" in window &&
window.arguments[0] instanceof Ci.nsIDialogParamBlock) {
state = JSON.parse(window.arguments[0].GetString(0));
let args = window.arguments;
if (args && args[0] instanceof Ci.nsIDialogParamBlock) {
args = args[0];
} else {
// If this Scratchpad window doesn't have any arguments, horrible
// things might happen so we need to report an error.
Cu.reportError(this.strings. GetStringFromName("scratchpad.noargs"));
}
this._instanceId = args.GetString(0);
let state = args.GetString(1) || null;
if (state) {
state = JSON.parse(state);
this.setState(state);
initialText = state.text;
}
@ -1217,8 +1236,13 @@ var Scratchpad = {
}
this.resetContext();
this.gBrowser.selectedBrowser.removeEventListener("load",
this._reloadAndRunEvent, true);
// This event is created only after user uses 'reload and run' feature.
if (this._reloadAndRunEvent) {
this.gBrowser.selectedBrowser.removeEventListener("load",
this._reloadAndRunEvent, true);
}
this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
this._onDirtyChanged);
PreferenceObserver.uninit();
@ -1282,25 +1306,14 @@ var Scratchpad = {
* there are unsaved changes.
*
* @param nsIDOMEvent aEvent
* @param function aCallback
* Optional function you want to call when file is saved/closed.
* Used mainly for tests.
*/
onClose: function SP_onClose(aEvent)
onClose: function SP_onClose(aEvent, aCallback)
{
if (this._skipClosePrompt) {
return;
}
this.promptSave(function(aShouldClose, aSaved, aStatus) {
let shouldClose = aShouldClose;
if (aSaved && !Components.isSuccessCode(aStatus)) {
shouldClose = false;
}
if (shouldClose) {
this._skipClosePrompt = true;
window.close();
}
}.bind(this));
aEvent.preventDefault();
this.close(aCallback);
},
/**
@ -1319,7 +1332,6 @@ var Scratchpad = {
}
if (shouldClose) {
this._skipClosePrompt = true;
window.close();
}
if (aCallback) {

View File

@ -35,6 +35,7 @@ MOCHITEST_BROWSER_FILES = \
browser_scratchpad_bug756681_display_non_error_exceptions.js \
browser_scratchpad_bug_751744_revert_to_saved.js \
browser_scratchpad_bug740948_reload_and_run.js \
browser_scratchpad_bug_661762_wrong_window_focus.js \
head.js \
include $(topsrcdir)/config/rules.mk

View File

@ -36,7 +36,8 @@ function runTests()
scratchpad.setText(error);
scratchpad.display();
is(scratchpad.getText(),
error + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
error + openComment +
"Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
"error display output");
scratchpad.setText(message);
@ -46,7 +47,8 @@ function runTests()
scratchpad.setText(error);
scratchpad.run();
is(scratchpad.getText(),
error + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
error + openComment +
"Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
"error display output");
finish();

View File

@ -41,7 +41,8 @@ function runTests()
scratchpad.setText(error1);
scratchpad.display();
is(scratchpad.getText(),
error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
error1 + openComment +
"Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
"error display output");
// Display error2, throw "A thrown string"
@ -75,7 +76,8 @@ function runTests()
scratchpad.setText(error1);
scratchpad.run();
is(scratchpad.getText(),
error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
error1 + openComment +
"Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
"error run output");
// Run error2, throw "A thrown string"

View File

@ -9,7 +9,7 @@ let NetUtil = tempScope.NetUtil;
let FileUtils = tempScope.FileUtils;
// only finish() when correct number of tests are done
const expected = 5;
const expected = 6;
var count = 0;
function done()
{
@ -69,6 +69,7 @@ function testSavedFile()
function testUnsaved()
{
testUnsavedFileCancel();
testCancelAfterLoad();
testUnsavedFileSave();
testUnsavedFileDontSave();
}
@ -89,6 +90,34 @@ function testUnsavedFileCancel()
}, {noFocus: true});
}
// Test a regression where our confirmation dialog wasn't appearing
// after openFile calls. See bug 801982.
function testCancelAfterLoad()
{
openScratchpad(function(win) {
win.Scratchpad.setRecentFile(gFile);
win.Scratchpad.openFile(0);
win.Scratchpad.editor.dirty = true;
promptButton = win.BUTTON_POSITION_CANCEL;
let EventStub = {
called: false,
preventDefault: function() {
EventStub.called = true;
}
};
win.Scratchpad.onClose(EventStub, function() {
ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
ok(EventStub.called, "aEvent.preventDefault was called");
win.Scratchpad.editor.dirty = false;
win.close();
done();
});
}, {noFocus: true});
}
function testUnsavedFileSave()
{
openScratchpad(function(win) {

View File

@ -0,0 +1,93 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let tempScope = {};
Cu.import("resource:///modules/HUDService.jsm", tempScope);
let HUDService = tempScope.HUDService;
function test()
{
waitForExplicitFinish();
// To test for this bug we open a Scratchpad window, save its
// reference and then open another one. This way the first window
// loses its focus.
//
// Then we open a web console and execute a console.log statement
// from the first Scratch window (that's why we needed to save its
// reference).
//
// Then we wait for our message to appear in the console and click
// on the location link. After that we check which Scratchpad window
// is currently active (it should be the older one).
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openScratchpad(function () {
let sw = gScratchpadWindow;
openScratchpad(function () {
function onWebConsoleOpen(subj) {
Services.obs.removeObserver(onWebConsoleOpen,
"web-console-created");
subj.QueryInterface(Ci.nsISupportsString);
let hud = HUDService.getHudReferenceById(subj.data);
hud.jsterm.clearOutput(true);
executeSoon(testFocus.bind(null, sw, hud));
}
Services.obs.
addObserver(onWebConsoleOpen, "web-console-created", false);
HUDService.consoleUI.toggleHUD();
});
});
}, true);
content.location = "data:text/html;charset=utf8,<p>test window focus for Scratchpad.";
}
function testFocus(sw, hud) {
let sp = sw.Scratchpad;
function onMessage(subj) {
Services.obs.removeObserver(onMessage, "web-console-message-created");
var loc = hud.jsterm.outputNode.querySelector(".webconsole-location");
ok(loc, "location element exists");
is(loc.value, sw.Scratchpad.uniqueName + ":1",
"location value is correct");
sw.addEventListener("focus", function onFocus() {
sw.removeEventListener("focus", onFocus, true);
let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
ok(win, "there are active Scratchpad windows");
is(win.Scratchpad.uniqueName, sw.Scratchpad.uniqueName,
"correct window is in focus");
// gScratchpadWindow will be closed automatically but we need to
// close the second window ourselves.
sw.close();
finish();
}, true);
// Simulate a click on the "Scratchpad/N:1" link.
EventUtils.synthesizeMouse(loc, 2, 2, {}, hud.iframeWindow);
}
// Sending messages to web console is an asynchronous operation. That's
// why we have to setup an observer here.
Services.obs.addObserver(onMessage, "web-console-message-created", false);
sp.setText("console.log('foo');");
let [selection, error, result] = sp.run();
is(selection, "console.log('foo');", "selection is correct");
is(error, undefined, "error is correct");
is(result, undefined, "result is correct");
}

View File

@ -5,6 +5,7 @@
// only finish() when correct number of tests are done
const expected = 3;
var count = 0;
var lastUniqueName = null;
function done()
{
@ -21,6 +22,19 @@ function test()
testOpenInvalidState();
}
function testUniqueName(name)
{
ok(name, "Scratchpad has a uniqueName");
if (lastUniqueName === null) {
lastUniqueName = name;
return;
}
ok(name !== lastUniqueName,
"Unique name for this instance differs from the last one.");
}
function testOpen()
{
openScratchpad(function(win) {
@ -28,6 +42,7 @@ function testOpen()
isnot(win.Scratchpad.getText(), null, "Default text should not be null");
is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT,
"Default execution context is content");
testUniqueName(win.Scratchpad.uniqueName);
win.close();
done();
@ -46,6 +61,7 @@ function testOpenWithState()
is(win.Scratchpad.filename, state.filename, "Filename loaded from state");
is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state");
is(win.Scratchpad.getText(), state.text, "Content loaded from state");
testUniqueName(win.Scratchpad.uniqueName);
win.close();
done();

View File

@ -1231,6 +1231,7 @@ SourceEditor.prototype = {
// If the caret is not at the closing bracket "}", find the index of the
// opening bracket "{" for the current code block.
if (matchingIndex == -1 || matchingIndex > caretOffset) {
matchingIndex = -1;
let text = this.getText();
let closingOffset = text.indexOf("}", caretOffset);
while (closingOffset > -1) {
@ -1241,18 +1242,34 @@ SourceEditor.prototype = {
}
closingOffset = text.indexOf("}", closingOffset + 1);
}
// Moving to the previous code block starting bracket if caret not inside
// any code block.
if (matchingIndex == -1) {
let lastClosingOffset = text.lastIndexOf("}", caretOffset);
while (lastClosingOffset > -1) {
let closingMatchingIndex =
this._getMatchingBracketIndex(lastClosingOffset);
if (closingMatchingIndex < caretOffset &&
closingMatchingIndex != -1) {
matchingIndex = closingMatchingIndex;
break;
}
lastClosingOffset = text.lastIndexOf("}", lastClosingOffset - 1);
}
}
}
if (matchingIndex > -1) {
this.setCaretOffset(matchingIndex);
this.setCaretOffset(matchingIndex + 1);
}
return true;
},
/**
* Moves the cursor to the matching closing bracket if at corresponding opening
* bracket, otherwise move to the closing bracket for the current block of code.
* Moves the cursor to the matching closing bracket if at corresponding
* opening bracket, otherwise move to the closing bracket for the current
* block of code.
*
* @private
*/
@ -1271,6 +1288,7 @@ SourceEditor.prototype = {
// If the caret is not at the opening bracket "{", find the index of the
// closing bracket "}" for the current code block.
if (matchingIndex == -1 || matchingIndex < caretOffset) {
matchingIndex = -1;
let text = this.getText();
let openingOffset = text.lastIndexOf("{", caretOffset);
while (openingOffset > -1) {
@ -1281,6 +1299,20 @@ SourceEditor.prototype = {
}
openingOffset = text.lastIndexOf("{", openingOffset - 1);
}
// Moving to the next code block ending bracket if caret not inside
// any code block.
if (matchingIndex == -1) {
let nextOpeningIndex = text.indexOf("{", caretOffset + 1);
while (nextOpeningIndex > -1) {
let openingMatchingIndex =
this._getMatchingBracketIndex(nextOpeningIndex);
if (openingMatchingIndex > caretOffset) {
matchingIndex = openingMatchingIndex;
break;
}
nextOpeningIndex = text.indexOf("{", nextOpeningIndex + 1);
}
}
}
if (matchingIndex > -1) {

View File

@ -30,6 +30,7 @@ MOCHITEST_BROWSER_FILES = \
browser_bug725430_comment_uncomment.js \
browser_bug731721_debugger_stepping.js \
browser_bug729960_block_bracket_jump.js \
browser_bug744021_next_prev_bracket_jump.js \
head.js \
include $(topsrcdir)/config/rules.mk

View File

@ -58,13 +58,13 @@ function test() {
"JS : Jump to closing bracket of the code block when caret at block start");
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 19,
is(editor.getCaretOffset(), 20,
"JS : Jump to opening bracket of the code block when caret at block end");
// Setting caret at Line 10 start.
editor.setCaretOffset(161);
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 19,
is(editor.getCaretOffset(), 20,
"JS : Jump to opening bracket of code block when inside the function");
editor.setCaretOffset(161);
@ -80,7 +80,7 @@ function test() {
editor.setCaretOffset(67);
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 61,
is(editor.getCaretOffset(), 62,
"JS : Jump to opening bracket in a nested function with caret inside");
let CSSText = "#object {\n" +
@ -98,13 +98,13 @@ function test() {
"CSS : Jump to closing bracket of the code block when caret at block start");
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 8,
is(editor.getCaretOffset(), 9,
"CSS : Jump to opening bracket of the code block when caret at block end");
// Setting caret at Line 3 start.
editor.setCaretOffset(28);
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 8,
is(editor.getCaretOffset(), 9,
"CSS : Jump to opening bracket of code block when inside the function");
editor.setCaretOffset(28);

View File

@ -0,0 +1,104 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test() {
let temp = {};
Cu.import("resource:///modules/source-editor.jsm", temp);
let SourceEditor = temp.SourceEditor;
waitForExplicitFinish();
let editor;
const windowUrl = "data:text/xml;charset=utf8,<?xml version='1.0'?><window " +
"xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' " +
"title='test for bug 744021' width='600' height='500'><hbox flex='1'/>" +
"</window>";
const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable," +
"dialog=no";
let testWin = Services.ww.openWindow(null, windowUrl, "_blank",
windowFeatures, null);
testWin.addEventListener("load", function onWindowLoad() {
testWin.removeEventListener("load", onWindowLoad, false);
waitForFocus(initEditor, testWin);
}, false);
function initEditor()
{
let hbox = testWin.document.querySelector("hbox");
editor = new SourceEditor();
editor.init(hbox, {showLineNumbers: true}, editorLoaded);
}
function editorLoaded()
{
editor.focus();
let JSText = "function foo() {\n" +
" \n" +
" function level2() {\n" +
" \n" +
" function level3() {\n" +
" \n" +
" }\n" +
" }\n" +
" function bar() { /* Block Level 2 */ }\n" +
"}\n" +
"function baz() {\n" +
" \n" +
"}";
editor.setMode(SourceEditor.MODES.JAVASCRIPT);
editor.setText(JSText);
// Setting caret at end of line 11 (function baz() {).
editor.setCaretOffset(147);
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 16,
"JS : Jump to opening bracket of previous sibling block when no parent");
EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 129,
"JS : Jump to closing bracket of same code block");
EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 151,
"JS : Jump to closing bracket of next sibling code block");
let CSSText = "#object1 {\n" +
" property: value;\n" +
" /* comment */\n" +
"}\n" +
".class1 {\n" +
" property: value;\n" +
"}";
editor.setMode(SourceEditor.MODES.CSS);
editor.setText(CSSText);
// Setting caret at Line 5 end (.class1 {).
editor.setCaretOffset(57);
EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 10,
"CSS : Jump to opening bracket of previous sibling code block");
EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 46,
"CSS : Jump to closing bracket of same code block");
EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
is(editor.getCaretOffset(), 77,
"CSS : Jump to closing bracket of next sibling code block");
editor.destroy();
testWin.close();
testWin = editor = null;
waitForFocus(finish, window);
}
}

View File

@ -113,10 +113,17 @@ HUD_SERVICE.prototype =
* The xul:tab element.
* @param boolean aAnimated
* True if you want to animate the opening of the Web console.
* @param object aOptions
* Options for the Web Console:
* - host
* Server to connect to.
* - port
* Port to connect to.
* @return object
* The new HeadsUpDisplay instance.
*/
activateHUDForContext: function HS_activateHUDForContext(aTab, aAnimated)
activateHUDForContext:
function HS_activateHUDForContext(aTab, aAnimated, aOptions)
{
let hudId = "hud_" + aTab.linkedPanel;
if (hudId in this.hudReferences) {
@ -132,7 +139,7 @@ HUD_SERVICE.prototype =
gBrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect, false);
window.addEventListener("unload", this.onWindowUnload, false);
let hud = new WebConsole(aTab);
let hud = new WebConsole(aTab, aOptions);
this.hudReferences[hudId] = hud;
if (!aAnimated || hud.consolePanel) {
@ -493,13 +500,19 @@ HUD_SERVICE.prototype =
*
* @param nsIDOMElement aTab
* The xul:tab for which you want the WebConsole object.
* @param object aOptions
* Web Console options: host and port, for the remote Web console.
*/
function WebConsole(aTab)
function WebConsole(aTab, aOptions = {})
{
this.tab = aTab;
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + this.tab.linkedPanel;
this.remoteHost = aOptions.host;
this.remotePort = aOptions.port;
this._onIframeLoad = this._onIframeLoad.bind(this);
this._initUI();
}
@ -1017,7 +1030,8 @@ var HeadsUpDisplayUICommands = {
}
},
toggleHUD: function UIC_toggleHUD() {
toggleHUD: function UIC_toggleHUD(aOptions)
{
var window = HUDService.currentContext();
var gBrowser = window.gBrowser;
var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
@ -1046,11 +1060,52 @@ var HeadsUpDisplayUICommands = {
}
}
else {
HUDService.activateHUDForContext(gBrowser.selectedTab, true);
HUDService.activateHUDForContext(gBrowser.selectedTab, true, aOptions);
HUDService.animate(hudId, ANIMATE_IN);
}
},
toggleRemoteHUD: function UIC_toggleRemoteHUD()
{
if (this.getOpenHUD()) {
this.toggleHUD();
return;
}
let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
let check = { value: false };
let input = { value: host + ":" + port };
let result = Services.prompt.prompt(null,
l10n.getStr("remoteWebConsolePromptTitle"),
l10n.getStr("remoteWebConsolePromptMessage"),
input, null, check);
if (!result) {
return;
}
let parts = input.value.split(":");
if (parts.length != 2) {
return;
}
[host, port] = parts;
if (!host.length || !port.length) {
return;
}
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
this.toggleHUD({
host: host,
port: port,
});
},
/**
* Find the hudId for the active chrome window.
* @return string|null

View File

@ -152,6 +152,7 @@ function testNext() {
pos++;
if (pos < TESTS.length) {
waitForSuccess({
timeout: 10000,
name: "test #" + pos + " successful finish",
validatorFn: function()
{
@ -211,6 +212,8 @@ function onDOMNodeInserted(aEvent) {
}
function test() {
requestLongerTimeout(2);
addTab("data:text/html;charset=utf-8,Web Console test for bug 595934 - message categories coverage.");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);

View File

@ -21,6 +21,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -360,8 +363,11 @@ WebConsoleFrame.prototype = {
*/
_initConnection: function WCF__initConnection()
{
this.proxy = new WebConsoleConnectionProxy(this);
this.proxy.initServer();
this.proxy = new WebConsoleConnectionProxy(this, {
host: this.owner.remoteHost,
port: this.owner.remotePort,
});
this.proxy.connect(function() {
this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
this._onInitComplete();
@ -959,9 +965,9 @@ WebConsoleFrame.prototype = {
/**
* Filter the console node from the output node if it is a repeat. Console
* messages are filtered from the output if and only if they match the
* immediately preceding message. The output node's last occurrence should
* have its timestamp updated.
* messages are filtered from the output if they match the immediately
* preceding message that came from the same source. The output node's
* last occurrence should have its timestamp updated.
*
* @param nsIDOMNode aNode
* The message node to be filtered or not.
@ -972,11 +978,31 @@ WebConsoleFrame.prototype = {
{
let lastMessage = this.outputNode.lastChild;
// childNodes[2] is the description element
if (lastMessage && lastMessage.childNodes[2] &&
!aNode.classList.contains("webconsole-msg-inspector") &&
aNode.childNodes[2].textContent ==
lastMessage.childNodes[2].textContent) {
if (!lastMessage) {
return false;
}
let body = aNode.querySelector(".webconsole-msg-body");
let lastBody = lastMessage.querySelector(".webconsole-msg-body");
if (aNode.classList.contains("webconsole-msg-inspector")) {
return false;
}
if (!body || !lastBody) {
return false;
}
if (body.textContent == lastBody.textContent) {
let loc = aNode.querySelector(".webconsole-location");
let lastLoc = lastMessage.querySelector(".webconsole-location");
if (loc && lastLoc) {
if (loc.getAttribute("value") !== lastLoc.getAttribute("value")) {
return false;
}
}
this.mergeFilteredMessageNode(lastMessage, aNode);
return true;
}
@ -2367,11 +2393,20 @@ WebConsoleFrame.prototype = {
let locationNode = this.document.createElementNS(XUL_NS, "label");
// Create the text, which consists of an abbreviated version of the URL
// plus an optional line number.
let text = WebConsoleUtils.abbreviateSourceURL(aSourceURL);
// plus an optional line number. Scratchpad URLs should not be abbreviated.
let text;
if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
text = aSourceURL;
}
else {
text = WebConsoleUtils.abbreviateSourceURL(aSourceURL);
}
if (aSourceLine) {
text += ":" + aSourceLine;
}
locationNode.setAttribute("value", text);
// Style appropriately.
@ -2383,10 +2418,16 @@ WebConsoleFrame.prototype = {
// Make the location clickable.
locationNode.addEventListener("click", function() {
if (aSourceURL == "Scratchpad") {
let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
if (win) {
win.focus();
if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
let wins = Services.wm.getEnumerator("devtools:scratchpad");
while (wins.hasMoreElements()) {
let win = wins.getNext();
if (win.Scratchpad.uniqueName === aSourceURL) {
win.focus();
return;
}
}
}
else if (locationNode.parentNode.category == CATEGORY_CSS) {
@ -3859,10 +3900,14 @@ CommandController.prototype = {
* @constructor
* @param object aWebConsole
* The Web Console instance that owns this connection proxy.
* @param object aOptions
* Connection options: host and port.
*/
function WebConsoleConnectionProxy(aWebConsole)
function WebConsoleConnectionProxy(aWebConsole, aOptions = {})
{
this.owner = aWebConsole;
this.remoteHost = aOptions.host;
this.remotePort = aOptions.port;
this._onPageError = this._onPageError.bind(this);
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
@ -3938,7 +3983,15 @@ WebConsoleConnectionProxy.prototype = {
*/
connect: function WCCP_connect(aCallback)
{
let transport = DebuggerServer.connectPipe();
let transport;
if (this.remoteHost) {
transport = debuggerSocketConnect(this.remoteHost, this.remotePort);
}
else {
this.initServer();
transport = DebuggerServer.connectPipe();
}
let client = this.client = new DebuggerClient(transport);
client.addListener("pageError", this._onPageError);
@ -3948,18 +4001,58 @@ WebConsoleConnectionProxy.prototype = {
client.addListener("fileActivity", this._onFileActivity);
client.addListener("locationChange", this._onLocationChange);
client.connect(function(aType, aTraits) {
client.listTabs(this._onListTabs.bind(this, aCallback));
}.bind(this));
},
/**
* The "listTabs" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
*/
_onListTabs: function WCCP__onListTabs(aCallback, aResponse)
{
let selectedTab;
if (this.remoteHost) {
let tabs = [];
for (let tab of aResponse.tabs) {
tabs.push(tab.title);
}
tabs.push(l10n.getStr("listTabs.globalConsoleActor"));
let selected = {};
let result = Services.prompt.select(null,
l10n.getStr("remoteWebConsoleSelectTabTitle"),
l10n.getStr("remoteWebConsoleSelectTabMessage"),
tabs.length, tabs, selected);
if (result && selected.value < aResponse.tabs.length) {
selectedTab = aResponse.tabs[selected.value];
}
}
else {
selectedTab = aResponse.tabs[aResponse.selected];
}
if (selectedTab) {
this._consoleActor = selectedTab.consoleActor;
this.owner.onLocationChange(selectedTab.url, selectedTab.title);
}
else {
this._consoleActor = aResponse.consoleActor;
}
let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
"FileActivity", "LocationChange"];
client.connect(function(aType, aTraits) {
client.listTabs(function(aResponse) {
let tab = aResponse.tabs[aResponse.selected];
this._consoleActor = tab.consoleActor;
this.owner.onLocationChange(tab.url, tab.title);
client.attachConsole(tab.consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback));
}.bind(this));
}.bind(this));
this.client.attachConsole(this._consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback));
},
/**

View File

@ -207,6 +207,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY webConsoleCmd.label "Web Console">
<!ENTITY webConsoleCmd.accesskey "W">
<!ENTITY webConsoleCmd.commandkey "k">
<!ENTITY remoteWebConsoleCmd.label "Remote Web Console">
<!ENTITY inspectMenu.label "Inspect">
<!ENTITY inspectMenu.accesskey "I">

View File

@ -71,6 +71,11 @@ confirmRevert.title=Revert Changes
# comment inside /* and */.
scratchpadIntro1=/*\n * This is a JavaScript Scratchpad.\n *\n * Enter some JavaScript, then Right Click or choose from the Execute Menu:\n * 1. Run to evaluate the selected text (%1$S),\n * 2. Inspect to bring up an Object Inspector on the result (%2$S), or,\n * 3. Display to insert the result in a comment after the selection. (%3$S)\n */\n\n
# LOCALIZATION NOTE (scratchpad.noargs): This error message is shown when
# Scratchpad instance is created without any arguments. Scratchpad window
# expects to receive its unique identifier as the first window argument.
scratchpad.noargs=Scratchpad was created without any arguments.
# LOCALIZATION NOTE (notification.browserContext): This is the message displayed
# over the top of the editor when the user has switched to browser context.
browserContext.notification=This scratchpad executes in the Browser context.

View File

@ -145,3 +145,23 @@ maxTimersExceeded=The maximum allowed number of timers in this page was exceeded
# Console and tries the Update button, but the new result no longer returns an
# object that can be inspected.
JSTerm.updateNotInspectable=After your input has been re-evaluated the result is no longer inspectable.
# LOCALIZATION NOTE (remoteWebConsolePromptTitle): The title displayed on the
# Web Console prompt asking for the remote host and port to connect to.
remoteWebConsolePromptTitle=Remote Connection
# LOCALIZATION NOTE (remoteWebConsolePromptMessage): The message displayed on the
# Web Console prompt asking for the remote host and port to connect to.
remoteWebConsolePromptMessage=Enter hostname and port number (host:port)
# LOCALIZATION NOTE (remoteWebConsoleSelectTabTitle): The title displayed on the
# Web Console prompt asking the user to pick a tab to attach to.
remoteWebConsoleSelectTabTitle=Tab list - Remote Connection
# LOCALIZATION NOTE (remoteWebConsoleSelectTabMessage): The message displayed on the
# Web Console prompt asking the user to pick a tab to attach to.
remoteWebConsoleSelectTabMessage=Select one of the tabs you want to attach to, or select the global console.
# LOCALIZATION NOTE (listTabs.globalConsoleActor): The string displayed for the
# global console in the tabs selection.
listTabs.globalConsoleActor=*Global Console*

View File

@ -1122,6 +1122,26 @@ nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame)
return NS_OK;
}
struct PreDestroyer
{
void Init(nsIEditor* aEditor)
{
mNewEditor = aEditor;
}
~PreDestroyer()
{
if (mNewEditor) {
mNewEditor->PreDestroy(true);
}
}
void Swap(nsCOMPtr<nsIEditor>& aEditor)
{
return mNewEditor.swap(aEditor);
}
private:
nsCOMPtr<nsIEditor> mNewEditor;
};
nsresult
nsTextEditorState::PrepareEditor(const nsAString *aValue)
{
@ -1168,12 +1188,14 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue)
bool shouldInitializeEditor = false;
nsCOMPtr<nsIEditor> newEditor; // the editor that we might create
nsresult rv = NS_OK;
PreDestroyer preDestroyer;
if (!mEditor) {
shouldInitializeEditor = true;
// Create an editor
newEditor = do_CreateInstance(kTextEditorCID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
preDestroyer.Init(newEditor);
// Make sure we clear out the non-breaking space before we initialize the editor
rv = mBoundFrame->UpdateValueDisplay(false, true);
@ -1302,7 +1324,7 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue)
if (shouldInitializeEditor) {
// Hold on to the newly created editor
mEditor = newEditor;
preDestroyer.Swap(mEditor);
}
// If we have a default value, insert it under the div we created

View File

@ -104,7 +104,7 @@ nsTextEditRules::Init(nsPlaintextEditor *aEditor)
mEditor = aEditor; // we hold a non-refcounted reference back to our editor
nsCOMPtr<nsISelection> selection;
mEditor->GetSelection(getter_AddRefs(selection));
NS_ASSERTION(selection, "editor cannot get selection");
NS_WARN_IF_FALSE(selection, "editor cannot get selection");
// Put in a magic br if needed. This method handles null selection,
// which should never happen anyway

View File

@ -425,6 +425,34 @@ SuspectDOMExpandos(nsPtrHashKey<JSObject> *key, void *arg)
return PL_DHASH_NEXT;
}
bool
CanSkipWrappedJS(nsXPCWrappedJS *wrappedJS)
{
JSObject *obj = wrappedJS->GetJSObjectPreserveColor();
// If traversing wrappedJS wouldn't release it, nor
// cause any other objects to be added to the graph, no
// need to add it to the graph at all.
if (nsCCUncollectableMarker::sGeneration &&
(!obj || !xpc_IsGrayGCThing(obj)) &&
!wrappedJS->IsSubjectToFinalization() &&
wrappedJS->GetRootWrapper() == wrappedJS) {
if (!wrappedJS->IsAggregatedToNative()) {
return true;
} else {
nsISupports* agg = wrappedJS->GetAggregatedNativeObject();
nsXPCOMCycleCollectionParticipant* cp = nullptr;
CallQueryInterface(agg, &cp);
nsISupports* canonical = nullptr;
agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
reinterpret_cast<void**>(&canonical));
if (cp && canonical && cp->CanSkipInCC(canonical)) {
return true;
}
}
}
return false;
}
void
XPCJSRuntime::AddXPConnectRoots(nsCycleCollectionTraversalCallback &cb)
{
@ -458,15 +486,8 @@ XPCJSRuntime::AddXPConnectRoots(nsCycleCollectionTraversalCallback &cb)
for (XPCRootSetElem *e = mWrappedJSRoots; e ; e = e->GetNextRoot()) {
nsXPCWrappedJS *wrappedJS = static_cast<nsXPCWrappedJS*>(e);
JSObject *obj = wrappedJS->GetJSObjectPreserveColor();
// If traversing wrappedJS wouldn't release it, nor
// cause any other objects to be added to the graph, no
// need to add it to the graph at all.
if (nsCCUncollectableMarker::sGeneration &&
!cb.WantAllTraces() && (!obj || !xpc_IsGrayGCThing(obj)) &&
!wrappedJS->IsSubjectToFinalization() &&
wrappedJS->GetRootWrapper() == wrappedJS &&
!wrappedJS->IsAggregatedToNative()) {
if (!cb.WantAllTraces() &&
CanSkipWrappedJS(wrappedJS)) {
continue;
}

View File

@ -296,50 +296,53 @@ nsTextControlFrame::EnsureEditorInitialized()
// Make sure that editor init doesn't do things that would kill us off
// (especially off the script blockers it'll create for its DOM mutations).
nsAutoScriptBlocker scriptBlocker;
{
nsAutoScriptBlocker scriptBlocker;
// Time to mess with our security context... See comments in GetValue()
// for why this is needed.
nsCxPusher pusher;
pusher.PushNull();
// Time to mess with our security context... See comments in GetValue()
// for why this is needed.
nsCxPusher pusher;
pusher.PushNull();
// Make sure that we try to focus the content even if the method fails
class EnsureSetFocus {
public:
explicit EnsureSetFocus(nsTextControlFrame* aFrame)
: mFrame(aFrame) {}
~EnsureSetFocus() {
if (nsContentUtils::IsFocusedContent(mFrame->GetContent()))
mFrame->SetFocus(true, false);
}
private:
nsTextControlFrame *mFrame;
};
EnsureSetFocus makeSureSetFocusHappens(this);
// Make sure that we try to focus the content even if the method fails
class EnsureSetFocus {
public:
explicit EnsureSetFocus(nsTextControlFrame* aFrame)
: mFrame(aFrame) {}
~EnsureSetFocus() {
if (nsContentUtils::IsFocusedContent(mFrame->GetContent()))
mFrame->SetFocus(true, false);
}
private:
nsTextControlFrame *mFrame;
};
EnsureSetFocus makeSureSetFocusHappens(this);
#ifdef DEBUG
// Make sure we are not being called again until we're finished.
// If reentrancy happens, just pretend that we don't have an editor.
const EditorInitializerEntryTracker tracker(*this);
NS_ASSERTION(!tracker.EnteredMoreThanOnce(),
"EnsureEditorInitialized has been called while a previous call was in progress");
// Make sure we are not being called again until we're finished.
// If reentrancy happens, just pretend that we don't have an editor.
const EditorInitializerEntryTracker tracker(*this);
NS_ASSERTION(!tracker.EnteredMoreThanOnce(),
"EnsureEditorInitialized has been called while a previous call was in progress");
#endif
// Create an editor for the frame, if one doesn't already exist
nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
NS_ASSERTION(txtCtrl, "Content not a text control element");
nsresult rv = txtCtrl->CreateEditor();
NS_ENSURE_SUCCESS(rv, rv);
// Create an editor for the frame, if one doesn't already exist
nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
NS_ASSERTION(txtCtrl, "Content not a text control element");
nsresult rv = txtCtrl->CreateEditor();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(weakFrame.IsAlive());
// Turn on mUseEditor so that subsequent calls will use the
// editor.
mUseEditor = true;
// Turn on mUseEditor so that subsequent calls will use the
// editor.
mUseEditor = true;
// Set the selection to the beginning of the text field.
if (weakFrame.IsAlive()) {
SetSelectionEndPoints(0, 0);
// Set the selection to the beginning of the text field.
if (weakFrame.IsAlive()) {
SetSelectionEndPoints(0, 0);
}
}
NS_ENSURE_STATE(weakFrame.IsAlive());
return NS_OK;
}
@ -1089,8 +1092,10 @@ nsTextControlFrame::GetSelectionRange(int32_t* aSelectionStart,
return NS_OK;
}
nsContentUtils::GetSelectionInTextControl(typedSel,
GetRootNodeAndInitializeEditor(), *aSelectionStart, *aSelectionEnd);
mozilla::dom::Element* root = GetRootNodeAndInitializeEditor();
NS_ENSURE_STATE(root);
nsContentUtils::GetSelectionInTextControl(typedSel, root,
*aSelectionStart, *aSelectionEnd);
return NS_OK;
}

View File

@ -212,10 +212,9 @@ var NetworkHelper =
*/
getWindowForRequest: function NH_getWindowForRequest(aRequest)
{
let loadContext = this.getRequestLoadContext(aRequest);
if (loadContext) {
return loadContext.associatedWindow;
}
try {
return this.getRequestLoadContext(aRequest).associatedWindow;
} catch (ex) { }
return null;
},
@ -227,18 +226,13 @@ var NetworkHelper =
*/
getRequestLoadContext: function NH_getRequestLoadContext(aRequest)
{
if (aRequest && aRequest.notificationCallbacks) {
try {
return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) { }
}
try {
return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) { }
if (aRequest && aRequest.loadGroup
&& aRequest.loadGroup.notificationCallbacks) {
try {
return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) { }
}
try {
return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) { }
return null;
},

View File

@ -2590,16 +2590,16 @@ _global.NetworkResponseListener = NetworkResponseListener;
* location changes.
*
* @constructor
* @param object aBrowser
* The xul:browser for which we need to track location changes.
* @param object aWindow
* The window for which we need to track location changes.
* @param object aOwner
* The listener owner which needs to implement two methods:
* - onFileActivity(aFileURI)
* - onLocationChange(aState, aTabURI, aPageTitle)
*/
function ConsoleProgressListener(aBrowser, aOwner)
function ConsoleProgressListener(aWindow, aOwner)
{
this.browser = aBrowser;
this.window = aWindow;
this.owner = aOwner;
}
@ -2637,6 +2637,8 @@ ConsoleProgressListener.prototype = {
*/
_initialized: false,
_webProgress: null,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference]),
@ -2650,8 +2652,13 @@ ConsoleProgressListener.prototype = {
return;
}
this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIWebProgress);
this._webProgress.addProgressListener(this,
Ci.nsIWebProgress.NOTIFY_STATE_ALL);
this._initialized = true;
this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
},
/**
@ -2768,8 +2775,7 @@ ConsoleProgressListener.prototype = {
let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
// Skip non-interesting states.
if (!isNetwork || !isWindow ||
aProgress.DOMWindow != this.browser.contentWindow) {
if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
return;
}
@ -2777,9 +2783,8 @@ ConsoleProgressListener.prototype = {
this.owner.onLocationChange("start", aRequest.URI.spec, "");
}
else if (isStop) {
let window = this.browser.contentWindow;
this.owner.onLocationChange("stop", window.location.href,
window.document.title);
this.owner.onLocationChange("stop", this.window.location.href,
this.window.document.title);
}
},
@ -2801,11 +2806,15 @@ ConsoleProgressListener.prototype = {
this._fileActivity = false;
this._locationChange = false;
if (this.browser.removeProgressListener) {
this.browser.removeProgressListener(this);
try {
this._webProgress.removeProgressListener(this);
}
catch (ex) {
// This can throw during browser shutdown.
}
this.browser = null;
this._webProgress = null;
this.window = null;
this.owner = null;
},
};

View File

@ -47,15 +47,20 @@ XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIStorage",
* @constructor
* @param object aConnection
* The connection to the client, DebuggerServerConnection.
* @param object [aTabActor]
* Optional, the parent tab actor. This must be an instance of
* BrowserTabActor.
* @param object [aParentActor]
* Optional, the parent actor.
*/
function WebConsoleActor(aConnection, aTabActor)
function WebConsoleActor(aConnection, aParentActor)
{
this.conn = aConnection;
if (aTabActor instanceof BrowserTabActor) {
this._browser = aTabActor.browser;
if (aParentActor instanceof BrowserTabActor &&
aParentActor.browser instanceof Ci.nsIDOMWindow) {
this._window = aParentActor.browser;
}
else if (aParentActor instanceof BrowserTabActor &&
aParentActor.browser instanceof Ci.nsIDOMElement) {
this._window = aParentActor.browser.contentWindow;
}
else {
this._window = Services.wm.getMostRecentWindow("navigator:browser");
@ -73,14 +78,6 @@ function WebConsoleActor(aConnection, aTabActor)
WebConsoleActor.prototype =
{
/**
* The xul:browser we work with. This is only available when the Web Console
* actor is a tab actor.
* @private
* @type nsIDOMElement
*/
_browser: null,
/**
* Tells if this Web Console actor is a global actor or not.
* @private
@ -137,7 +134,7 @@ WebConsoleActor.prototype =
* The content window we work with.
* @type nsIDOMWindow
*/
get window() this._browser ? this._browser.contentWindow : this._window,
get window() this._window,
_window: null,
@ -220,7 +217,7 @@ WebConsoleActor.prototype =
this._objectActorsPool = null;
this._networkEventActorsPool = null;
this._sandboxLocation = this.sandbox = null;
this.conn = this._browser = this._window = null;
this.conn = this._window = null;
},
/**
@ -332,29 +329,23 @@ WebConsoleActor.prototype =
startedListeners.push(listener);
break;
case "FileActivity":
if (this._isGlobalActor) {
// The ConsoleProgressListener cannot listen for global events.
// See bug 798764.
break;
}
if (!this.consoleProgressListener) {
this.consoleProgressListener =
new ConsoleProgressListener(this._browser, this);
new ConsoleProgressListener(this.window, this);
}
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
MONITOR_FILE_ACTIVITY);
startedListeners.push(listener);
break;
case "LocationChange":
if (this._isGlobalActor) {
break;
}
if (!this.consoleProgressListener) {
this.consoleProgressListener =
new ConsoleProgressListener(this._browser, this);
new ConsoleProgressListener(this.window, this);
}
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
MONITOR_LOCATION_CHANGE);
startedListeners.push(listener);
break;
}
}
return {

View File

@ -19,6 +19,7 @@ MOCHITEST_CHROME_FILES = \
test_object_actor.html \
test_network_get.html \
test_network_post.html \
test_file_uri.html \
network_requests_iframe.html \
data.json \
common.js \

View File

@ -0,0 +1,97 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test for file activity tracking</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript;version=1.8" src="common.js"></script>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test for file activity tracking</p>
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
let gState;
let gTmpFile;
function doFileActivity()
{
info("doFileActivity");
let fileContent = "<p>hello world from bug 798764";
gTmpFile = FileUtils.getFile("TmpD", ["bug798764.html"]);
gTmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
let fout = FileUtils.openSafeFileOutputStream(gTmpFile,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let fileContentStream = converter.convertToInputStream(fileContent);
NetUtil.asyncCopy(fileContentStream, fout, addIframe);
}
function addIframe(aStatus)
{
ok(Components.isSuccessCode(aStatus),
"the temporary file was saved successfully");
let iframe = document.createElement("iframe");
iframe.src = NetUtil.newURI(gTmpFile).spec;
document.body.appendChild(iframe);
}
function startTest()
{
removeEventListener("load", startTest);
attachConsole(["FileActivity"], onAttach);
}
function onAttach(aState, aResponse)
{
gState = aState;
gState.dbgClient.addListener("fileActivity", onFileActivity);
doFileActivity();
}
function onFileActivity(aType, aPacket)
{
is(aPacket.from, gState.actor, "fileActivity actor");
gState.dbgClient.removeListener("fileActivity", onFileActivity);
ok(/bug798764\.html$/.test(aPacket.uri), "file URI match");
testEnd();
}
function testEnd()
{
if (gTmpFile) {
gTmpFile.remove(false);
gTmpFile = null;
}
if (gState) {
closeDebugger(gState, function() {
gState = null;
SimpleTest.finish();
});
} else {
SimpleTest.finish();
}
}
addEventListener("load", startTest);
</script>
</body>
</html>