Merge mozilla-central to mozilla-inbound

This commit is contained in:
Ed Morley 2012-07-26 13:25:04 +01:00
commit bc713a9541
37 changed files with 1401 additions and 1232 deletions

View File

@ -5,6 +5,10 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/**
* B2G-specific actors that extend BrowserRootActor and BrowserTabActor,
* overriding some of their methods.
*/
/**
* The function that creates the root actor. DebuggerServer expects to find this
@ -24,67 +28,52 @@ function createRootActor(connection) {
* The conection to the client.
*/
function DeviceRootActor(connection) {
this.conn = connection;
this._tabActors = new WeakMap();
this._tabActorPool = null;
this._actorFactories = null;
BrowserRootActor.call(this, connection);
this.browser = Services.wm.getMostRecentWindow('navigator:browser');
}
DeviceRootActor.prototype = {
/**
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
*/
sayHello: function DRA_sayHello() {
return {
from: 'root',
applicationType: 'browser',
traits: []
};
},
DeviceRootActor.prototype = new BrowserRootActor();
/**
* Disconnects the actor from the browser window.
*/
disconnect: function DRA_disconnect() {
let actor = this._tabActors.get(this.browser);
if (actor) {
actor.exit();
}
},
/**
* Disconnects the actor from the browser window.
*/
DeviceRootActor.prototype.disconnect = function DRA_disconnect() {
let actor = this._tabActors.get(this.browser);
if (actor) {
actor.exit();
}
};
/**
* Handles the listTabs request. Builds a list of actors for the single
* tab (window) running in the process. The actors will survive
* until at least the next listTabs request.
*/
onListTabs: function DRA_onListTabs() {
let actor = this._tabActors.get(this.browser);
if (!actor) {
actor = new DeviceTabActor(this.conn, this.browser);
// this.actorID is set by ActorPool when an actor is put into one.
actor.parentID = this.actorID;
this._tabActors.set(this.browser, actor);
}
let actorPool = new ActorPool(this.conn);
actorPool.addActor(actor);
// Now drop the old actorID -> actor map. Actors that still mattered were
// added to the new map, others will go away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
return {
'from': 'root',
'selected': 0,
'tabs': [actor.grip()]
};
/**
* Handles the listTabs request. Builds a list of actors for the single
* tab (window) running in the process. The actors will survive
* until at least the next listTabs request.
*/
DeviceRootActor.prototype.onListTabs = function DRA_onListTabs() {
let actor = this._tabActors.get(this.browser);
if (!actor) {
actor = new DeviceTabActor(this.conn, this.browser);
// this.actorID is set by ActorPool when an actor is put into one.
actor.parentID = this.actorID;
this._tabActors.set(this.browser, actor);
}
let actorPool = new ActorPool(this.conn);
actorPool.addActor(actor);
// Now drop the old actorID -> actor map. Actors that still mattered were
// added to the new map, others will go away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
return {
'from': 'root',
'selected': 0,
'tabs': [actor.grip()]
};
};
/**
@ -104,219 +93,58 @@ DeviceRootActor.prototype.requestTypes = {
* The browser instance that contains this tab.
*/
function DeviceTabActor(connection, browser) {
this.conn = connection;
this._browser = browser;
BrowserTabActor.call(this, connection, browser);
}
DeviceTabActor.prototype = {
get browser() {
return this._browser;
},
DeviceTabActor.prototype = new BrowserTabActor();
get exited() {
return !this.browser;
},
get attached() {
return !!this._attached
},
_tabPool: null,
get tabActorPool() {
return this._tabPool;
},
_contextPool: null,
get contextActorPool() {
return this._contextPool;
},
/**
* Add the specified breakpoint to the default actor pool connection, in order
* to be alive as long as the server is.
*
* @param BreakpointActor actor
* The actor object.
*/
addToBreakpointPool: function DTA_addToBreakpointPool(actor) {
this.conn.addActor(actor);
},
/**
* Remove the specified breakpint from the default actor pool.
*
* @param string actor
* The actor ID.
*/
removeFromBreakpointPool: function DTA_removeFromBreakpointPool(actor) {
this.conn.removeActor(actor);
},
actorPrefix: 'tab',
grip: function DTA_grip() {
dbg_assert(!this.exited,
'grip() should not be called on exited browser actor.');
dbg_assert(this.actorID,
'tab should have an actorID.');
return {
'actor': this.actorID,
'title': this.browser.title,
'url': this.browser.document.documentURI
}
},
/**
* Called when the actor is removed from the connection.
*/
disconnect: function DTA_disconnect() {
this._detach();
},
/**
* Called by the root actor when the underlying tab is closed.
*/
exit: function DTA_exit() {
if (this.exited) {
return;
}
if (this.attached) {
this._detach();
this.conn.send({
'from': this.actorID,
'type': 'tabDetached'
});
}
this._browser = null;
},
/**
* Does the actual work of attaching to a tab.
*/
_attach: function DTA_attach() {
if (this._attached) {
return;
}
// Create a pool for tab-lifetime actors.
dbg_assert(!this._tabPool, 'Should not have a tab pool if we were not attached.');
this._tabPool = new ActorPool(this.conn);
this.conn.addActorPool(this._tabPool);
// ... and a pool for context-lifetime actors.
this._pushContext();
this._attached = true;
},
/**
* Creates a thread actor and a pool for context-lifetime actors. It then sets
* up the content window for debugging.
*/
_pushContext: function DTA_pushContext() {
dbg_assert(!this._contextPool, "Can't push multiple contexts");
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this.threadActor = new ThreadActor(this);
this._addDebuggees(this.browser.wrappedJSObject);
this._contextPool.addActor(this.threadActor);
},
/**
* Add the provided window and all windows in its frame tree as debuggees.
*/
_addDebuggees: function DTA__addDebuggees(content) {
this.threadActor.addDebuggee(content);
let frames = content.frames;
for (let i = 0; i < frames.length; i++) {
this._addDebuggees(frames[i]);
}
},
/**
* Exits the current thread actor and removes the context-lifetime actor pool.
* The content window is no longer being debugged after this call.
*/
_popContext: function DTA_popContext() {
dbg_assert(!!this._contextPool, 'No context to pop.');
this.conn.removeActorPool(this._contextPool);
this._contextPool = null;
this.threadActor.exit();
this.threadActor = null;
},
/**
* Does the actual work of detaching from a tab.
*/
_detach: function DTA_detach() {
if (!this.attached) {
return;
}
this._popContext();
// Shut down actors that belong to this tab's pool.
this.conn.removeActorPool(this._tabPool);
this._tabPool = null;
this._attached = false;
},
// Protocol Request Handlers
onAttach: function DTA_onAttach(aRequest) {
if (this.exited) {
return { type: 'exited' };
}
this._attach();
return { type: 'tabAttached', threadActor: this.threadActor.actorID };
},
onDetach: function DTA_onDetach(aRequest) {
if (!this.attached) {
return { error: 'wrongState' };
}
this._detach();
return { type: 'detached' };
},
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest: function DTA_preNest() {
let windowUtils = this.browser
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest: function DTA_postNest(aNestData) {
let windowUtils = this.browser
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
DeviceTabActor.prototype.grip = function DTA_grip() {
dbg_assert(!this.exited,
'grip() should not be called on exited browser actor.');
dbg_assert(this.actorID,
'tab should have an actorID.');
return {
'actor': this.actorID,
'title': this.browser.title,
'url': this.browser.document.documentURI
}
};
/**
* The request types this actor can handle.
* Creates a thread actor and a pool for context-lifetime actors. It then sets
* up the content window for debugging.
*/
DeviceTabActor.prototype.requestTypes = {
'attach': DeviceTabActor.prototype.onAttach,
'detach': DeviceTabActor.prototype.onDetach
DeviceTabActor.prototype._pushContext = function DTA_pushContext() {
dbg_assert(!this._contextPool, "Can't push multiple contexts");
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this.threadActor = new ThreadActor(this);
this._addDebuggees(this.browser.wrappedJSObject);
this._contextPool.addActor(this.threadActor);
};
// Protocol Request Handlers
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
DeviceTabActor.prototype.preNest = function DTA_preNest() {
let windowUtils = this.browser
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
};
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
DeviceTabActor.prototype.postNest = function DTA_postNest(aNestData) {
let windowUtils = this.browser
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
};

View File

@ -544,6 +544,7 @@ function startDebugger() {
if (!DebuggerServer.initialized) {
// Allow remote connections.
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');
}

View File

@ -88,17 +88,18 @@
<command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
<command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();"/>
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
<command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focus();" disabled="true"/>
<command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
<command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
<command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();"/>
<command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();"/>
<command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();"/>
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
<command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();"/>
<command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
<command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true" hidden="true"/>
<command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/>
<command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/>
<command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
<command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();" disabled="true" hidden="true"/>
<command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
<command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
<command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
<command id="Tools:Sanitize"
oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
<command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
@ -190,8 +191,7 @@
label="&devToolbarMenu.label;"
type="checkbox" autocheck="false"
command="Tools:DevToolbar"
key="key_devToolbar"
disabled="true" hidden="true"/>
key="key_devToolbar"/>
<broadcaster id="devtoolsMenuBroadcaster_WebConsole"
label="&webConsoleCmd.label;"
type="checkbox" autocheck="false"
@ -201,48 +201,40 @@
label="&inspectMenu.label;"
type="checkbox" autocheck="false"
command="Tools:Inspect"
key="key_inspect"
disabled="true" hidden="true"/>
key="key_inspect"/>
<broadcaster id="devtoolsMenuBroadcaster_Debugger"
label="&debuggerMenu.label2;"
type="checkbox" autocheck="false"
command="Tools:Debugger"
key="key_debugger"
disabled="true" hidden="true"/>
key="key_debugger"/>
<broadcaster id="devtoolsMenuBroadcaster_RemoteDebugger"
label="&remoteDebuggerMenu.label;"
command="Tools:RemoteDebugger"
disabled="true" hidden="true"/>
command="Tools:RemoteDebugger"/>
<broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
label="&chromeDebuggerMenu.label;"
command="Tools:ChromeDebugger"
disabled="true" hidden="true"/>
command="Tools:ChromeDebugger"/>
<broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
label="&scratchpad.label;"
command="Tools:Scratchpad"
key="key_scratchpad"
disabled="true" hidden="true"/>
key="key_scratchpad"/>
<broadcaster id="devtoolsMenuBroadcaster_StyleEditor"
label="&styleeditor.label;"
type="checkbox" autocheck="false"
command="Tools:StyleEditor"
key="key_styleeditor"
disabled="true" hidden="true"/>
key="key_styleeditor"/>
<broadcaster id="devtoolsMenuBroadcaster_ResponsiveUI"
label="&responsiveDesignTool.label;"
type="checkbox" autocheck="false"
command="Tools:ResponsiveUI"
key="key_responsiveUI"
disabled="true" hidden="true"/>
key="key_responsiveUI"/>
<broadcaster id="devtoolsMenuBroadcaster_PageSource"
label="&pageSourceCmd.label;"
key="key_viewSource"
command="View:PageSource"/>
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
hidden="true"
label="&errorConsoleCmd.label;"
key="key_errorConsole"
oncommand="toJavaScriptConsole();"/>
command="Tools:ErrorConsole"/>
<broadcaster id="devtoolsMenuBroadcaster_GetMoreTools"
label="&getMoreDevtoolsCmd.label;"
oncommand="openUILinkIn('https://addons.mozilla.org/firefox/collections/mozilla/webdeveloper/', 'tab');"/>
@ -292,7 +284,7 @@
<key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
#endif
<key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
<key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/>
<key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" command="Tools:ErrorConsole" modifiers="accel,shift"/>
<key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift"
keytext="&devToolbar.keytext;" command="Tools:DevToolbarFocus"/>
<key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();"

View File

@ -1402,9 +1402,9 @@ var gBrowserInit = {
// Enable developer toolbar?
let devToolbarEnabled = gPrefService.getBoolPref("devtools.toolbar.enabled");
if (devToolbarEnabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_DevToolbar");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:DevToolbar");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
document.getElementById("Tools:DevToolbarFocus").removeAttribute("disabled");
// Show the toolbar if it was previously visible
@ -1416,25 +1416,25 @@ var gBrowserInit = {
// Enable Inspector?
let enabled = gPrefService.getBoolPref("devtools.inspector.enabled");
if (enabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_Inspect");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:Inspect");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Debugger?
let enabled = gPrefService.getBoolPref("devtools.debugger.enabled");
if (enabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_Debugger");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:Debugger");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Remote Debugger?
let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
if (enabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_RemoteDebugger");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:RemoteDebugger");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Chrome Debugger?
@ -1442,34 +1442,34 @@ var gBrowserInit = {
gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
gPrefService.getBoolPref("devtools.debugger.remote-enabled");
if (enabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_ChromeDebugger");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:ChromeDebugger");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Error Console?
// XXX Temporarily always-enabled, see bug 601201
let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled");
if (consoleEnabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_ErrorConsole");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:ErrorConsole");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Scratchpad in the UI, if the preference allows this.
let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
if (scratchpadEnabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_Scratchpad");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:Scratchpad");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
// Enable Style Editor?
let styleEditorEnabled = gPrefService.getBoolPref(StyleEditor.prefEnabledName);
if (styleEditorEnabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_StyleEditor");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:StyleEditor");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
#ifdef MENUBAR_CAN_AUTOHIDE
@ -1484,9 +1484,9 @@ var gBrowserInit = {
// Enable Responsive UI?
let responsiveUIEnabled = gPrefService.getBoolPref("devtools.responsiveUI.enabled");
if (responsiveUIEnabled) {
let broadcaster = document.getElementById("devtoolsMenuBroadcaster_ResponsiveUI");
broadcaster.removeAttribute("disabled");
broadcaster.removeAttribute("hidden");
let cmd = document.getElementById("Tools:ResponsiveUI");
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
}
let appMenuButton = document.getElementById("appmenu-button");

View File

@ -1070,6 +1070,14 @@
onclick="contentAreaClick(event, false);"/>
<statuspanel id="statusbar-display" inactive="true"/>
</vbox>
<splitter id="devtools-side-splitter" hidden="true"/>
<vbox id="devtools-sidebar-box" hidden="true"
style="min-width: 18em; width: 22em; max-width: 42em;" persist="width">
<toolbar id="devtools-sidebar-toolbar"
class="devtools-toolbar"
nowindowdrag="true"/>
<deck id="devtools-sidebar-deck" flex="1"/>
</vbox>
<splitter id="social-sidebar-splitter"
class="chromeclass-extrachrome"
observes="socialSidebarBroadcaster"/>
@ -1081,14 +1089,6 @@
flex="1"
style="min-width: 14em; width: 18em; max-width: 36em;"/>
</vbox>
<splitter id="devtools-side-splitter" hidden="true"/>
<vbox id="devtools-sidebar-box" hidden="true"
style="min-width: 18em; width: 22em; max-width: 42em;" persist="width">
<toolbar id="devtools-sidebar-toolbar"
class="devtools-toolbar"
nowindowdrag="true"/>
<deck id="devtools-sidebar-deck" flex="1"/>
</vbox>
<vbox id="browser-border-end" hidden="true" layer="true"/>
</hbox>

View File

@ -10,11 +10,8 @@ relativesrcdir = browser/devtools/tilt/test
include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_FILES = head.js
# browser_tilt* disabled on Linux due to bug 759157
ifneq (gtk2,$(MOZ_WIDGET_TOOLKIT))
MOCHITEST_BROWSER_FILES += \
MOCHITEST_BROWSER_FILES = \
head.js \
browser_tilt_01_lazy_getter.js \
browser_tilt_02_notifications-seq.js \
browser_tilt_02_notifications.js \
@ -60,6 +57,5 @@ MOCHITEST_BROWSER_FILES += \
browser_tilt_visualizer.js \
browser_tilt_zoom.js \
$(NULL)
endif
include $(topsrcdir)/config/rules.mk

View File

@ -538,7 +538,7 @@ var WebConsoleUtils = {
value = aObject[propName];
presentable = this.presentableValueFor(value);
}
catch (ex) {
catch (ex) {
continue;
}
}
@ -735,6 +735,8 @@ const OPEN_CLOSE_BODY = {
"(": ")",
};
const MAX_COMPLETIONS = 256;
/**
* Analyses a given string to find the last statement that is interesting for
* later completion.
@ -895,9 +897,9 @@ function JSPropertyProvider(aScope, aInputValue)
return null;
}
// If obj is undefined or null, then there is no chance to run completion
// on it. Exit here.
if (typeof obj === "undefined" || obj === null) {
// If obj is undefined or null (which is what "== null" does),
// then there is no chance to run completion on it. Exit here.
if (obj == null) {
return null;
}
@ -918,9 +920,9 @@ function JSPropertyProvider(aScope, aInputValue)
matchProp = properties[0].trimLeft();
}
// If obj is undefined or null, then there is no chance to run
// completion on it. Exit here.
if (typeof obj === "undefined" || obj === null) {
// If obj is undefined or null (which is what "== null" does),
// then there is no chance to run completion on it. Exit here.
if (obj == null) {
return null;
}
@ -929,12 +931,7 @@ function JSPropertyProvider(aScope, aInputValue)
return null;
}
let matches = [];
for (let prop in obj) {
if (prop.indexOf(matchProp) == 0) {
matches.push(prop);
}
}
let matches = Object.keys(getMatchedProps(obj, matchProp));
return {
matchProp: matchProp,
@ -942,5 +939,55 @@ function JSPropertyProvider(aScope, aInputValue)
};
}
/**
* Get all accessible properties on this object.
* Filter those properties by name.
* Take only a certain number of those.
*
* @param object obj
* Object whose properties we want to collect.
*
* @param string matchProp
* Filter for properties that match this one.
* Defaults to the empty string (which always matches).
*
* @return object
* Object whose keys are all accessible properties on the object.
*/
function getMatchedProps(aObj, aMatchProp = "")
{
let c = MAX_COMPLETIONS;
let names = {}; // Using an Object to avoid duplicates.
let ownNames = Object.getOwnPropertyNames(aObj);
for (let i = 0; i < ownNames.length; i++) {
if (ownNames[i].indexOf(aMatchProp) == 0) {
if (names[ownNames[i]] != true) {
c--;
if (c < 0) {
return names;
}
names[ownNames[i]] = true;
}
}
}
// We need to recursively go up the prototype chain.
aObj = Object.getPrototypeOf(aObj);
if (aObj !== null) {
let parentScope = getMatchedProps(aObj, aMatchProp);
for (let name in parentScope) {
if (names[name] != true) {
c--;
if (c < 0) {
return names;
}
names[name] = true;
}
}
}
return names;
}
return JSPropertyProvider;
})(WebConsoleUtils);

View File

@ -35,33 +35,58 @@ function consoleOpened(aHud) {
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 4, "popup.itemCount is correct");
// 4 values, and the following properties:
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
// hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString
// toSource unwatch valueOf watch constructor.
is(popup.itemCount, 18, "popup.itemCount is correct");
let sameItems = popup.getItems();
is(sameItems.every(function(aItem, aIndex) {
return aItem.label == "item" + aIndex;
}), true, "getItems returns back the same items");
let sameItems = popup.getItems().map(function(e) {return e.label;});
ok(sameItems.every(function(prop, index) {
return [
"__defineGetter__",
"__defineSetter__",
"__lookupGetter__",
"__lookupSetter__",
"constructor",
"hasOwnProperty",
"isPrototypeOf",
"item0",
"item1",
"item2",
"item3",
"propertyIsEnumerable",
"toLocaleString",
"toSource",
"toString",
"unwatch",
"valueOf",
"watch",
][index] === prop}), "getItems returns the items we expect");
is(popup.selectedIndex, -1, "no index is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item0", "item0 is selected");
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "item1", "item1 is selected");
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
is(popup.selectedItem.label, "__defineSetter__", "__defineSetter__ is selected");
is(completeNode.value, prefix + "__defineSetter__",
"completeNode.value holds __defineSetter__");
EventUtils.synthesizeKey("VK_UP", {});
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item0", "item0 is selected");
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
@ -83,7 +108,7 @@ function autocompletePopupHidden()
ok(!popup.isOpen, "popup is not open");
is(inputNode.value, "window.foobarBug585991.item0",
is(inputNode.value, "window.foobarBug585991.__defineGetter__",
"completion was successful after VK_TAB");
ok(!completeNode.value, "completeNode is empty");
@ -93,16 +118,17 @@ function autocompletePopupHidden()
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 4, "popup.itemCount is correct");
is(popup.itemCount, 18, "popup.itemCount is correct");
is(popup.selectedIndex, -1, "no index is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item0", "item0 is selected");
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
@ -140,29 +166,31 @@ function testReturnKey()
ok(popup.isOpen, "popup is open");
is(popup.itemCount, 4, "popup.itemCount is correct");
is(popup.itemCount, 18, "popup.itemCount is correct");
is(popup.selectedIndex, -1, "no index is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item0", "item0 is selected");
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "item1", "item1 is selected");
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
is(popup.selectedItem.label, "__defineSetter__", "__defineSetter__ is selected");
is(completeNode.value, prefix + "__defineSetter__",
"completeNode.value holds __defineSetter__");
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
ok(!popup.isOpen, "popup is not open after VK_RETURN");
is(inputNode.value, "window.foobarBug585991.item1",
is(inputNode.value, "window.foobarBug585991.__defineSetter__",
"completion was successful after VK_RETURN");
ok(!completeNode.value, "completeNode is empty");

View File

@ -34,8 +34,12 @@ function consoleOpened(aHud) {
ok(popup.isOpen, "popup is open");
// |props| values, and the following properties:
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
// constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
// toLocaleString toSource toString unwatch valueOf watch.
let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
is(popup.itemCount, props.length, "popup.itemCount is correct");
is(popup.itemCount, 14 + props.length, "popup.itemCount is correct");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);

View File

@ -50,6 +50,14 @@ function testCompletion(hud) {
is(input.selectionEnd, 8, "end selection is alright");
is(jsterm.completeNode.value.replace(/ /g, ""), "", "'docu' completed");
// Test typing 'window.O' and press tab.
input.value = "window.O";
input.setSelectionRange(8, 8);
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
yield;
is(input.value, "window.Object", "'window.O' tab completion");
// Test typing 'document.getElem'.
input.value = "document.getElem";
input.setSelectionRange(16, 16);

View File

@ -454,7 +454,7 @@ addonListDesc=List installed add-ons
# LOCALIZATION NOTE (addonListTypeDesc) A very short description of the
# 'addon list <type>' 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.
addonListTypeDesc=Select an addon type
addonListTypeDesc=Select an add-on type
# LOCALIZATION NOTE (addonListDictionaryHeading, addonListExtensionHeading,
# addonListLocaleHeading, addonListPluginHeading, addonListThemeHeading,
@ -465,18 +465,18 @@ addonListExtensionHeading=The following extensions are currently installed:
addonListLocaleHeading=The following locales are currently installed:
addonListPluginHeading=The following plugins are currently installed:
addonListThemeHeading=The following themes are currently installed:
addonListAllHeading=The following addons are currently installed:
addonListUnknownHeading=The following addons of the selected type are currently installed:
addonListAllHeading=The following add-ons are currently installed:
addonListUnknownHeading=The following add-ons of the selected type are currently installed:
# LOCALIZATION NOTE (addonNameDesc) A very short description of the
# name parameter of numerous addon commands. This string is designed to be shown
# name parameter of numerous add-on commands. This string is designed to be shown
# in a menu alongside the command name, which is why it should be as short as
# possible.
addonNameDesc=The name of the add-on
# LOCALIZATION NOTE (addonNoneOfType) Used in the output of the 'addon list'
# command when a search for addons of a particular type were not found.
addonNoneOfType=There are no addons of that type installed.
# command when a search for add-ons of a particular type were not found.
addonNoneOfType=There are no add-ons of that type installed.
# LOCALIZATION NOTE (addonEnableDesc) A very short description of the
# 'addon enable <type>' command. This string is designed to be shown in a menu
@ -484,12 +484,12 @@ addonNoneOfType=There are no addons of that type installed.
addonEnableDesc=Enable the specified add-on
# LOCALIZATION NOTE (addonAlreadyEnabled) Used in the output of the
# 'addon enable' command when an attempt is made to enable an addon is already
# 'addon enable' command when an attempt is made to enable an add-on is already
# enabled.
addonAlreadyEnabled=%S is already enabled.
# LOCALIZATION NOTE (addonEnabled) Used in the output of the 'addon enable'
# command when an addon is enabled.
# command when an add-on is enabled.
addonEnabled=%S enabled.
# LOCALIZATION NOTE (addonDisableDesc) A very short description of the
@ -498,12 +498,12 @@ addonEnabled=%S enabled.
addonDisableDesc=Disable the specified add-on
# LOCALIZATION NOTE (addonAlreadyDisabled) Used in the output of the
# 'addon disable' command when an attempt is made to disable an addon is already
# 'addon disable' command when an attempt is made to disable an add-on is already
# disabled.
addonAlreadyDisabled=%S is already disabled.
# LOCALIZATION NOTE (addonDisabled) Used in the output of the 'addon disable'
# command when an addon is disabled.
# command when an add-on is disabled.
addonDisabled=%S disabled.
# LOCALIZATION NOTE (exportDesc) A very short description of the 'export'

View File

@ -36,8 +36,6 @@ CPPSRCS = \
nsDOMDragEvent.cpp \
nsDOMMutationEvent.cpp \
nsDOMPopupBlockedEvent.cpp \
nsDOMDeviceLightEvent.cpp \
nsDOMDeviceOrientationEvent.cpp \
nsDOMDeviceMotionEvent.cpp \
nsDOMBeforeUnloadEvent.cpp \
nsDOMXULCommandEvent.cpp \
@ -65,7 +63,6 @@ CPPSRCS = \
nsDOMSettingsEvent.cpp \
nsDOMTouchEvent.cpp \
nsDOMCompositionEvent.cpp \
nsDOMApplicationEvent.cpp \
$(NULL)
ifdef MOZ_B2G_RIL

View File

@ -1,68 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsDOMApplicationEvent.h"
#include "nsContentUtils.h"
#include "DictionaryHelpers.h"
#include "nsDOMClassInfoID.h"
DOMCI_DATA(MozApplicationEvent, nsDOMMozApplicationEvent)
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMozApplicationEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMMozApplicationEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mApplication)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMMozApplicationEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mApplication)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMMozApplicationEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMMozApplicationEvent)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozApplicationEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
NS_IMPL_ADDREF_INHERITED(nsDOMMozApplicationEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMMozApplicationEvent, nsDOMEvent)
NS_IMETHODIMP
nsDOMMozApplicationEvent::GetApplication(mozIDOMApplication** aApplication)
{
NS_IF_ADDREF(*aApplication = mApplication);
return NS_OK;
}
NS_IMETHODIMP
nsDOMMozApplicationEvent::InitMozApplicationEvent(const nsAString& aType,
bool aCanBubble,
bool aCancelable,
mozIDOMApplication* aApplication)
{
nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
NS_ENSURE_SUCCESS(rv, rv);
mApplication = aApplication;
return NS_OK;
}
nsresult
nsDOMMozApplicationEvent::InitFromCtor(const nsAString& aType, JSContext* aCx, jsval* aVal)
{
mozilla::dom::MozApplicationEventInit d;
nsresult rv = d.Init(aCx, aVal);
NS_ENSURE_SUCCESS(rv, rv);
return InitMozApplicationEvent(aType, d.bubbles, d.cancelable, d.application);
}
nsresult
NS_NewDOMMozApplicationEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent* aEvent)
{
nsDOMMozApplicationEvent* e = new nsDOMMozApplicationEvent(aPresContext, aEvent);
return CallQueryInterface(e, aInstancePtrResult);
}

View File

@ -1,32 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsDOMApplicationEvent_h__
#define nsDOMApplicationEvent_h__
#include "nsIDOMApplicationRegistry.h"
#include "nsDOMEvent.h"
class nsDOMMozApplicationEvent : public nsDOMEvent,
public nsIDOMMozApplicationEvent
{
public:
nsDOMMozApplicationEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent) {}
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMMozApplicationEvent, nsDOMEvent)
// Forward to base class
NS_FORWARD_TO_NSDOMEVENT
NS_DECL_NSIDOMMOZAPPLICATIONEVENT
virtual nsresult InitFromCtor(const nsAString& aType, JSContext* aCx, jsval* aVal);
private:
nsCOMPtr<mozIDOMApplication> mApplication;
};
#endif // nsDOMContactChangeEvent_h__

View File

@ -1,58 +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/. */
#include "nsDOMClassInfoID.h"
#include "nsDOMDeviceLightEvent.h"
#include "DictionaryHelpers.h"
NS_IMPL_ADDREF_INHERITED(nsDOMDeviceLightEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMDeviceLightEvent, nsDOMEvent)
DOMCI_DATA(DeviceLightEvent, nsDOMDeviceLightEvent)
NS_INTERFACE_MAP_BEGIN(nsDOMDeviceLightEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceLightEvent)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceLightEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
NS_IMETHODIMP
nsDOMDeviceLightEvent::InitDeviceLightEvent(const nsAString & aEventTypeArg,
bool aCanBubbleArg,
bool aCancelableArg,
double aValue)
{
nsresult rv = nsDOMEvent::InitEvent(aEventTypeArg, aCanBubbleArg, aCancelableArg);
NS_ENSURE_SUCCESS(rv, rv);
mValue = aValue;
return NS_OK;
}
NS_IMETHODIMP
nsDOMDeviceLightEvent::GetValue(double *aValue)
{
NS_ENSURE_ARG_POINTER(aValue);
*aValue = mValue;
return NS_OK;
}
nsresult
nsDOMDeviceLightEvent::InitFromCtor(const nsAString& aType,
JSContext* aCx, jsval* aVal)
{
mozilla::dom::DeviceLightEventInit d;
nsresult rv = d.Init(aCx, aVal);
NS_ENSURE_SUCCESS(rv, rv);
return InitDeviceLightEvent(aType, d.bubbles, d.cancelable, d.value);
}
nsresult
NS_NewDOMDeviceLightEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent *aEvent)
{
NS_ENSURE_ARG_POINTER(aInstancePtrResult);
nsDOMDeviceLightEvent* it = new nsDOMDeviceLightEvent(aPresContext, aEvent);
return CallQueryInterface(it, aInstancePtrResult);
}

View File

@ -1,36 +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/. */
#ifndef nsDOMDeviceLightEvent_h__
#define nsDOMDeviceLightEvent_h__
#include "nsIDOMDeviceLightEvent.h"
#include "nsDOMEvent.h"
class nsDOMDeviceLightEvent
: public nsDOMEvent
, public nsIDOMDeviceLightEvent
{
public:
nsDOMDeviceLightEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent),
mValue(0) {}
NS_DECL_ISUPPORTS_INHERITED
// Forward to nsDOMEvent
NS_FORWARD_TO_NSDOMEVENT
// nsIDOMDeviceLightEvent Interface
NS_DECL_NSIDOMDEVICELIGHTEVENT
virtual nsresult InitFromCtor(const nsAString& aType,
JSContext* aCx,
jsval* aVal);
protected:
double mValue;
};
#endif

View File

@ -1,81 +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/. */
#include "nsDOMClassInfoID.h"
#include "nsDOMDeviceOrientationEvent.h"
NS_IMPL_ADDREF_INHERITED(nsDOMDeviceOrientationEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMDeviceOrientationEvent, nsDOMEvent)
DOMCI_DATA(DeviceOrientationEvent, nsDOMDeviceOrientationEvent)
NS_INTERFACE_MAP_BEGIN(nsDOMDeviceOrientationEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceOrientationEvent)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceOrientationEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
NS_IMETHODIMP nsDOMDeviceOrientationEvent::InitDeviceOrientationEvent(const nsAString & aEventTypeArg,
bool aCanBubbleArg,
bool aCancelableArg,
double aAlpha,
double aBeta,
double aGamma,
bool aAbsolute)
{
nsresult rv = nsDOMEvent::InitEvent(aEventTypeArg, aCanBubbleArg, aCancelableArg);
NS_ENSURE_SUCCESS(rv, rv);
mAlpha = aAlpha;
mBeta = aBeta;
mGamma = aGamma;
mAbsolute = aAbsolute;
return NS_OK;
}
NS_IMETHODIMP nsDOMDeviceOrientationEvent::GetAlpha(double *aAlpha)
{
NS_ENSURE_ARG_POINTER(aAlpha);
*aAlpha = mAlpha;
return NS_OK;
}
NS_IMETHODIMP nsDOMDeviceOrientationEvent::GetBeta(double *aBeta)
{
NS_ENSURE_ARG_POINTER(aBeta);
*aBeta = mBeta;
return NS_OK;
}
NS_IMETHODIMP nsDOMDeviceOrientationEvent::GetGamma(double *aGamma)
{
NS_ENSURE_ARG_POINTER(aGamma);
*aGamma = mGamma;
return NS_OK;
}
NS_IMETHODIMP nsDOMDeviceOrientationEvent::GetAbsolute(bool *aAbsolute)
{
NS_ENSURE_ARG_POINTER(aAbsolute);
*aAbsolute = mAbsolute;
return NS_OK;
}
nsresult NS_NewDOMDeviceOrientationEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent *aEvent)
{
NS_ENSURE_ARG_POINTER(aInstancePtrResult);
nsDOMDeviceOrientationEvent* it = new nsDOMDeviceOrientationEvent(aPresContext, aEvent);
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
return CallQueryInterface(it, aInstancePtrResult);
}

View File

@ -1,36 +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/. */
#ifndef nsDOMDeviceOrientationEvent_h__
#define nsDOMDeviceOrientationEvent_h__
#include "nsIDOMDeviceOrientationEvent.h"
#include "nsDOMEvent.h"
class nsDOMDeviceOrientationEvent : public nsDOMEvent,
public nsIDOMDeviceOrientationEvent
{
public:
nsDOMDeviceOrientationEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent),
mAlpha(0),
mBeta(0),
mGamma(0),
mAbsolute(true) {}
NS_DECL_ISUPPORTS_INHERITED
// Forward to nsDOMEvent
NS_FORWARD_TO_NSDOMEVENT
// nsIDOMDeviceOrientationEvent Interface
NS_DECL_NSIDOMDEVICEORIENTATIONEVENT
protected:
double mAlpha, mBeta, mGamma;
bool mAbsolute;
};
#endif

View File

@ -425,6 +425,24 @@ is(e.value, 1, "value should be 1");
document.dispatchEvent(e);
is(receivedEvent, e, "Wrong event!");
// DeviceOrientationEvent
e = new DeviceOrientationEvent("hello");
ok(e.type, "hello", "Wrong event type!");
ok(!e.isTrusted, "Event should not be trusted");
is(e.alpha, 0);
is(e.beta, 0);
is(e.gamma, 0);
is(e.absolute, false);
e = new DeviceOrientationEvent("hello", { alpha: 1, beta: 2, gamma: 3, absolute: true } );
ok(e.type, "hello", "Wrong event type!");
ok(!e.isTrusted, "Event should not be trusted");
is(e.alpha, 1);
is(e.beta, 2);
is(e.gamma, 3);
is(e.absolute, true);
document.dispatchEvent(e);
is(receivedEvent, e, "Wrong event!");
// MouseEvent

View File

@ -298,8 +298,6 @@
#define MOZ_GENERATED_EVENTS_INCLUDES
#include "GeneratedEvents.h"
#undef MOZ_GENERATED_EVENTS_INCLUDES
#include "nsIDOMDeviceLightEvent.h"
#include "nsIDOMDeviceOrientationEvent.h"
#include "nsIDOMDeviceMotionEvent.h"
#include "nsIDOMRange.h"
#include "nsIDOMNodeIterator.h"
@ -815,9 +813,6 @@ static nsDOMClassInfoData sClassInfoData[] = {
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(PopupBlockedEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
// Device Light
NS_DEFINE_CLASSINFO_DATA(DeviceLightEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
#define MOZ_GENERATED_EVENT_LIST
#define MOZ_GENERATED_EVENT(_event_interface) \
@ -826,9 +821,6 @@ static nsDOMClassInfoData sClassInfoData[] = {
#include "GeneratedEvents.h"
#undef MOZ_GENERATED_EVENT_LIST
// Device Orientation
NS_DEFINE_CLASSINFO_DATA(DeviceOrientationEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(DeviceMotionEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(DeviceAcceleration, nsDOMGenericSH,
@ -1641,8 +1633,6 @@ static nsDOMClassInfoData sClassInfoData[] = {
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(MozSettingsEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(MozApplicationEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
#ifdef MOZ_B2G_RIL
NS_DEFINE_CLASSINFO_DATA(MozWifiStatusChangeEvent, nsDOMGenericSH,
@ -1727,10 +1717,8 @@ static const nsContractIDMapData kConstructorMap[] =
NS_DEFINE_EVENT_CTOR(Event)
NS_DEFINE_EVENT_CTOR(MozSettingsEvent)
NS_DEFINE_EVENT_CTOR(MozApplicationEvent)
NS_DEFINE_EVENT_CTOR(UIEvent)
NS_DEFINE_EVENT_CTOR(MouseEvent)
NS_DEFINE_EVENT_CTOR(DeviceLightEvent)
#ifdef MOZ_B2G_RIL
NS_DEFINE_EVENT_CTOR(MozWifiStatusChangeEvent)
NS_DEFINE_EVENT_CTOR(MozWifiConnectionInfoEvent)
@ -1775,10 +1763,8 @@ static const nsConstructorFuncMapData kConstructorFuncMap[] =
NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozBlobBuilder, NS_NewBlobBuilder)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(Event)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MozSettingsEvent)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MozApplicationEvent)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(UIEvent)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MouseEvent)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(DeviceLightEvent)
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(StorageEvent)
#ifdef MOZ_B2G_RIL
NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MozWifiStatusChangeEvent)
@ -2600,11 +2586,6 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(DeviceLightEvent, nsIDOMDeviceLightEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceLightEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
#define MOZ_GENERATED_EVENT_LIST
#define MOZ_GENERATED_EVENT(_event_interface) \
DOM_CLASSINFO_MAP_BEGIN(_event_interface, nsIDOM##_event_interface) \
@ -2614,11 +2595,6 @@ nsDOMClassInfo::Init()
#include "GeneratedEvents.h"
#undef MOZ_GENERATED_EVENT_LIST
DOM_CLASSINFO_MAP_BEGIN(DeviceOrientationEvent, nsIDOMDeviceOrientationEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceOrientationEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(DeviceMotionEvent, nsIDOMDeviceMotionEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceMotionEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
@ -4409,11 +4385,6 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(MozApplicationEvent, nsIDOMMozApplicationEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozApplicationEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
#ifdef MOZ_B2G_RIL
DOM_CLASSINFO_MAP_BEGIN(MozWifiStatusChangeEvent, nsIDOMMozWifiStatusChangeEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozWifiStatusChangeEvent)

View File

@ -43,12 +43,10 @@ DOMCI_CLASS(DragEvent)
DOMCI_CLASS(KeyboardEvent)
DOMCI_CLASS(CompositionEvent)
DOMCI_CLASS(PopupBlockedEvent)
DOMCI_CLASS(DeviceLightEvent)
#define MOZ_GENERATED_EVENT_LIST
#define MOZ_GENERATED_EVENT(_event_interface) DOMCI_CLASS(_event_interface)
#include "GeneratedEvents.h"
#undef MOZ_GENERATED_EVENT_LIST
DOMCI_CLASS(DeviceOrientationEvent)
DOMCI_CLASS(DeviceMotionEvent)
DOMCI_CLASS(DeviceAcceleration)
DOMCI_CLASS(DeviceRotationRate)
@ -509,8 +507,6 @@ DOMCI_CLASS(MutationRecord)
DOMCI_CLASS(MozSettingsEvent)
DOMCI_CLASS(MozApplicationEvent)
#ifdef MOZ_B2G_RIL
DOMCI_CLASS(MozWifiStatusChangeEvent)
DOMCI_CLASS(MozWifiConnectionInfoEvent)

View File

@ -17,6 +17,7 @@ GRE_MODULE = 1
XPIDLSRCS = \
nsIDOMApplicationRegistry.idl \
nsIAppsService.idl \
nsIDOMMozApplicationEvent.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "domstubs.idl"
#include "nsIDOMEvent.idl"
#include "nsIDOMEventTarget.idl"
interface nsIDOMDOMRequest;
@ -43,22 +42,6 @@ interface mozIDOMApplication : nsISupports
nsIDOMDOMRequest uninstall();
};
[scriptable, builtinclass, uuid(8f2bfba8-f10e-4f63-a5e0-7a7056e1dbe6)]
interface nsIDOMMozApplicationEvent : nsIDOMEvent
{
readonly attribute mozIDOMApplication application;
[noscript] void initMozApplicationEvent(in DOMString aType,
in boolean aCanBubble,
in boolean aCancelable,
in mozIDOMApplication aApplication);
};
dictionary MozApplicationEventInit : EventInit
{
mozIDOMApplication application;
};
[scriptable, uuid(bd304874-d532-4e13-8034-544211445583)]
interface mozIDOMApplicationMgmt : nsISupports
{

View File

@ -0,0 +1,23 @@
/* 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/. */
#include "nsIDOMEvent.idl"
interface mozIDOMApplication;
[scriptable, builtinclass, uuid(8f2bfba8-f10e-4f63-a5e0-7a7056e1dbe6)]
interface nsIDOMMozApplicationEvent : nsIDOMEvent
{
readonly attribute mozIDOMApplication application;
[noscript] void initMozApplicationEvent(in DOMString aType,
in boolean aCanBubble,
in boolean aCancelable,
in mozIDOMApplication aApplication);
};
dictionary MozApplicationEventInit : EventInit
{
mozIDOMApplication application;
};

View File

@ -41,3 +41,10 @@ interface nsIDOMDeviceOrientationEvent : nsIDOMEvent
readonly attribute boolean absolute;
};
dictionary DeviceOrientationEventInit : EventInit
{
double alpha;
double beta;
double gamma;
boolean absolute;
};

View File

@ -204,10 +204,6 @@ NS_NewDOMMutationEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class
nsresult
NS_NewDOMPopupBlockedEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, nsEvent* aEvent);
nsresult
NS_NewDOMDeviceOrientationEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, nsEvent* aEvent);
nsresult
NS_NewDOMDeviceLightEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, nsEvent* aEvent);
nsresult
NS_NewDOMDeviceMotionEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, nsEvent* aEvent);
nsresult
NS_NewDOMTextEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsTextEvent* aEvent);

View File

@ -16,8 +16,6 @@ dictionaries = [
[ 'WifiConnectionInfoEventInit', 'nsIWifiEventInits.idl' ],
[ 'WifiStatusChangeEventInit', 'nsIWifiEventInits.idl' ],
[ 'GeoPositionOptions', 'nsIDOMGeoGeolocation.idl' ],
[ 'DeviceLightEventInit', 'nsIDOMDeviceLightEvent.idl' ],
[ 'MozApplicationEventInit', 'nsIDOMApplicationRegistry.idl' ],
[ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ],
[ 'XMLHttpRequestParameters', 'nsIXMLHttpRequest.idl' ],
[ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ]
@ -26,7 +24,8 @@ dictionaries = [
# include file names
special_includes = [
'nsContentUtils.h',
'XPCQuickStubs.h'
'XPCQuickStubs.h',
'nsIDOMApplicationRegistry.h'
]
# name of the type to not include using #include "typename.h"

View File

@ -304,6 +304,8 @@ def write_getter(a, iface, fd):
fd.write(" JSBool b;\n")
fd.write(" MOZ_ALWAYS_TRUE(JS_ValueToBoolean(aCx, v, &b));\n")
fd.write(" aDict.%s = b;\n" % a.name)
elif realtype.count("PRUint32"):
fd.write(" NS_ENSURE_STATE(JS_ValueToECMAUint32(aCx, v, &aDict.%s));\n" % a.name)
elif realtype.count("PRInt32"):
fd.write(" NS_ENSURE_STATE(JS_ValueToECMAInt32(aCx, v, &aDict.%s));\n" % a.name)
elif realtype.count("double"):

View File

@ -15,16 +15,21 @@ simple_events = [
'PopStateEvent',
'HashChangeEvent',
'CloseEvent',
'MozContactChangeEvent'
'MozContactChangeEvent',
'DeviceOrientationEvent',
'DeviceLightEvent',
'MozApplicationEvent'
]
""" include file names """
special_includes = [
'DictionaryHelpers.h',
'nsContentUtils.h'
'nsContentUtils.h',
'nsIDOMApplicationRegistry.h'
]
""" name of the type to not include using #include "typename.h" """
exclude_automatic_type_include = [
'nsISupports'
'nsISupports',
'mozIDOMApplication'
]

View File

@ -6098,6 +6098,7 @@ var RemoteDebugger = {
try {
if (!DebuggerServer.initialized) {
DebuggerServer.init(this._allowConnection);
DebuggerServer.addBrowserActors();
DebuggerServer.addActors("chrome://browser/content/dbg-browser-actors.js");
}

View File

@ -5,14 +5,16 @@
"use strict";
/**
* Fennec-specific actors.
* Fennec-specific root actor that extends BrowserRootActor and overrides some
* of its methods.
*/
var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
/**
* The function that creates the root actor. DebuggerServer expects to find this
* function in the loaded actors in order to initialize properly.
*/
function createRootActor(aConnection) {
return new BrowserRootActor(aConnection);
return new FennecRootActor(aConnection);
}
/**
@ -24,412 +26,94 @@ function createRootActor(aConnection) {
* @param aConnection DebuggerServerConnection
* The conection to the client.
*/
function BrowserRootActor(aConnection) {
this.conn = aConnection;
this._tabActors = new WeakMap();
this._tabActorPool = null;
this._actorFactories = null;
this.onTabClosed = this.onTabClosed.bind(this);
windowMediator.addListener(this);
function FennecRootActor(aConnection) {
BrowserRootActor.call(this, aConnection);
}
BrowserRootActor.prototype = {
/**
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
*/
sayHello: function BRA_sayHello() {
return { from: "root",
applicationType: "browser",
traits: [] };
},
/**
* Disconnects the actor from the browser window.
*/
disconnect: function BRA_disconnect() {
windowMediator.removeListener(this);
// We may have registered event listeners on browser windows to
// watch for tab closes, remove those.
let win = windowMediator.getMostRecentWindow("navigator:browser");
this.unwatchWindow(win);
// Signal our imminent shutdown.
let evt = win.document.createEvent("Event");
evt.initEvent("Debugger:Shutdown", true, false);
win.document.documentElement.dispatchEvent(evt);
},
/**
* Handles the listTabs request. Builds a list of actors
* for the tabs running in the process. The actors will survive
* until at least the next listTabs request.
*/
onListTabs: function BRA_onListTabs() {
// Get actors for all the currently-running tabs (reusing
// existing actors where applicable), and store them in
// an ActorPool.
let actorPool = new ActorPool(this.conn);
let actorList = [];
let win = windowMediator.getMostRecentWindow("navigator:browser");
this.browser = win.BrowserApp.selectedBrowser;
// Watch the window for tab closes so we can invalidate
// actors as needed.
this.watchWindow(win);
let tabs = win.BrowserApp.tabs;
let selected;
for each (let tab in tabs) {
let browser = tab.browser;
if (browser == this.browser) {
selected = actorList.length;
}
let actor = this._tabActors.get(browser);
if (!actor) {
actor = new BrowserTabActor(this.conn, browser);
actor.parentID = this.actorID;
this._tabActors.set(browser, actor);
}
actorPool.addActor(actor);
actorList.push(actor);
}
// Now drop the old actorID -> actor map. Actors that still
// mattered were added to the new map, others will go
// away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
return { "from": "root",
"selected": selected,
"tabs": [actor.grip()
for each (actor in actorList)] };
},
/**
* Watch a window that was visited during onListTabs for
* tab closures.
*/
watchWindow: function BRA_watchWindow(aWindow) {
let tabContainer = aWindow.document.getElementById("browsers");
tabContainer.addEventListener("TabClose",
this.onTabClosed,
false);
},
/**
* Stop watching a window for tab closes.
*/
unwatchWindow: function BRA_unwatchWindow(aWindow) {
let tabContainer = aWindow.document.getElementById("browsers");
tabContainer.removeEventListener("TabClose", this.onTabClosed);
this.exitTabActor(aWindow);
},
/**
* When a tab is closed, exit its tab actor. The actor
* will be dropped at the next listTabs request.
*/
onTabClosed: function BRA_onTabClosed(aEvent) {
this.exitTabActor(aEvent.target.browser);
},
/**
* Exit the tab actor of the specified tab.
*/
exitTabActor: function BRA_exitTabActor(aWindow) {
let actor = this._tabActors.get(aWindow);
if (actor) {
actor.exit();
}
},
// nsIWindowMediatorListener
onWindowTitleChange: function BRA_onWindowTitleChange(aWindow, aTitle) { },
onOpenWindow: function BRA_onOpenWindow(aWindow) { },
onCloseWindow: function BRA_onCloseWindow(aWindow) {
if (aWindow.BrowserApp) {
this.unwatchWindow(aWindow);
}
}
}
FennecRootActor.prototype = new BrowserRootActor();
/**
* The request types this actor can handle.
* Handles the listTabs request. Builds a list of actors
* for the tabs running in the process. The actors will survive
* until at least the next listTabs request.
*/
BrowserRootActor.prototype.requestTypes = {
"listTabs": BrowserRootActor.prototype.onListTabs
FennecRootActor.prototype.onListTabs = function FRA_onListTabs() {
// Get actors for all the currently-running tabs (reusing
// existing actors where applicable), and store them in
// an ActorPool.
let actorPool = new ActorPool(this.conn);
let actorList = [];
let win = windowMediator.getMostRecentWindow("navigator:browser");
this.browser = win.BrowserApp.selectedBrowser;
// Watch the window for tab closes so we can invalidate
// actors as needed.
this.watchWindow(win);
let tabs = win.BrowserApp.tabs;
let selected;
for each (let tab in tabs) {
let browser = tab.browser;
if (browser == this.browser) {
selected = actorList.length;
}
let actor = this._tabActors.get(browser);
if (!actor) {
actor = new BrowserTabActor(this.conn, browser);
actor.parentID = this.actorID;
this._tabActors.set(browser, actor);
}
actorPool.addActor(actor);
actorList.push(actor);
}
// Now drop the old actorID -> actor map. Actors that still
// mattered were added to the new map, others will go
// away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
return { "from": "root",
"selected": selected,
"tabs": [actor.grip()
for each (actor in actorList)] };
};
/**
* Creates a tab actor for handling requests to a browser tab, like attaching
* and detaching.
*
* @param aConnection DebuggerServerConnection
* The conection to the client.
* @param aBrowser browser
* The browser instance that contains this tab.
* Return the tab container for the specified window.
*/
function BrowserTabActor(aConnection, aBrowser)
{
this.conn = aConnection;
this._browser = aBrowser;
FennecRootActor.prototype.getTabContainer = function FRA_getTabContainer(aWindow) {
return aWindow.document.getElementById("browsers");
};
this._onWindowCreated = this.onWindowCreated.bind(this);
}
/**
* When a tab is closed, exit its tab actor. The actor
* will be dropped at the next listTabs request.
*/
FennecRootActor.prototype.onTabClosed = function FRA_onTabClosed(aEvent) {
this.exitTabActor(aEvent.target.browser);
};
// XXX (bug 710213): BrowserTabActor attach/detach/exit/disconnect is a
// *complete* mess, needs to be rethought asap.
BrowserTabActor.prototype = {
get browser() { return this._browser; },
get exited() { return !this._browser; },
get attached() { return !!this._attached },
_tabPool: null,
get tabActorPool() { return this._tabPool; },
_contextPool: null,
get contextActorPool() { return this._contextPool; },
/**
* Add the specified breakpoint to the default actor pool connection, in order
* to be alive as long as the server is.
*
* @param BreakpointActor aActor
* The actor object.
*/
addToBreakpointPool: function BTA_addToBreakpointPool(aActor) {
this.conn.addActor(aActor);
},
/**
* Remove the specified breakpint from the default actor pool.
*
* @param string aActor
* The actor ID.
*/
removeFromBreakpointPool: function BTA_removeFromBreakpointPool(aActor) {
this.conn.removeActor(aActor);
},
actorPrefix: "tab",
grip: function BTA_grip() {
dbg_assert(!this.exited,
"grip() shouldn't be called on exited browser actor.");
dbg_assert(this.actorID,
"tab should have an actorID.");
return { actor: this.actorID,
title: this._browser.contentTitle,
url: this._browser.currentURI.spec }
},
/**
* Called when the actor is removed from the connection.
*/
disconnect: function BTA_disconnect() {
this._detach();
},
/**
* Called by the root actor when the underlying tab is closed.
*/
exit: function BTA_exit() {
if (this.exited) {
return;
}
if (this.attached) {
this._detach();
this.conn.send({ from: this.actorID,
type: "tabDetached" });
}
this._browser = null;
},
/**
* Does the actual work of attching to a tab.
*/
_attach: function BTA_attach() {
if (this._attached) {
return;
}
// Create a pool for tab-lifetime actors.
dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
this._tabPool = new ActorPool(this.conn);
this.conn.addActorPool(this._tabPool);
// ... and a pool for context-lifetime actors.
this._pushContext();
// Watch for globals being created in this tab.
this._browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
this._attached = true;
},
/**
* Creates a thread actor and a pool for context-lifetime actors. It then sets
* up the content window for debugging.
*/
_pushContext: function BTA_pushContext() {
dbg_assert(!this._contextPool, "Can't push multiple contexts");
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this.threadActor = new ThreadActor(this);
this._addDebuggees(this._browser.contentWindow.wrappedJSObject);
this._contextPool.addActor(this.threadActor);
},
/**
* Add the provided window and all windows in its frame tree as debuggees.
*/
_addDebuggees: function BTA__addDebuggees(aWindow) {
this.threadActor.addDebuggee(aWindow);
let frames = aWindow.frames;
for (let i = 0; i < frames.length; i++) {
this._addDebuggees(frames[i]);
}
},
/**
* Exits the current thread actor and removes the context-lifetime actor pool.
* The content window is no longer being debugged after this call.
*/
_popContext: function BTA_popContext() {
dbg_assert(!!this._contextPool, "No context to pop.");
this.conn.removeActorPool(this._contextPool);
this._contextPool = null;
this.threadActor.exit();
this.threadActor = null;
},
/**
* Does the actual work of detaching from a tab.
*/
_detach: function BTA_detach() {
if (!this.attached) {
return;
}
this._browser.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
this._popContext();
// Shut down actors that belong to this tab's pool.
this.conn.removeActorPool(this._tabPool);
this._tabPool = null;
this._attached = false;
},
// Protocol Request Handlers
onAttach: function BTA_onAttach(aRequest) {
if (this.exited) {
return { type: "exited" };
}
this._attach();
return { type: "tabAttached", threadActor: this.threadActor.actorID };
},
onDetach: function BTA_onDetach(aRequest) {
if (!this.attached) {
return { error: "wrongState" };
}
this._detach();
return { type: "detached" };
},
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest: function BTA_preNest() {
if (!this._browser) {
// The tab is already closed.
return;
}
let windowUtils = this._browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest: function BTA_postNest(aNestData) {
if (!this._browser) {
// The tab is already closed.
return;
}
let windowUtils = this._browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
},
/**
* Handle location changes, by sending a tabNavigated notification to the
* client.
*/
onWindowCreated: function BTA_onWindowCreated(evt) {
if (evt.target === this._browser.contentDocument) {
if (this._attached) {
this.conn.send({ from: this.actorID, type: "tabNavigated",
url: this._browser.contentDocument.URL });
}
}
// nsIWindowMediatorListener
FennecRootActor.prototype.onCloseWindow = function FRA_onCloseWindow(aWindow) {
if (aWindow.BrowserApp) {
this.unwatchWindow(aWindow);
}
};
/**
* The request types this actor can handle.
*/
BrowserTabActor.prototype.requestTypes = {
"attach": BrowserTabActor.prototype.onAttach,
"detach": BrowserTabActor.prototype.onDetach
};
/**
* Registers handlers for new request types defined dynamically. This is used
* for example by add-ons to augment the functionality of the tab actor.
*
* @param aName string
* The name of the new request type.
* @param aFunction function
* The handler for this request type.
*/
DebuggerServer.addTabRequest = function DS_addTabRequest(aName, aFunction) {
BrowserTabActor.prototype.requestTypes[aName] = function(aRequest) {
if (!this.attached) {
return { error: "wrongState" };
}
return aFunction(this, aRequest);
}
FennecRootActor.prototype.requestTypes = {
"listTabs": FennecRootActor.prototype.onListTabs
};

View File

@ -132,13 +132,25 @@ ServerBSO.prototype = {
}
if (request.hasHeader("x-if-modified-since")) {
let headerModified = parseInt(request.getHeader("x-if-modified-since"));
let headerModified = parseInt(request.getHeader("x-if-modified-since"),
10);
CommonUtils.ensureMillisecondsTimestamp(headerModified);
if (headerModified >= this.modified) {
code = 304;
status = "Not Modified";
sendResponse();
return;
}
} else if (request.hasHeader("x-if-unmodified-since")) {
let requestModified = parseInt(request.getHeader("x-if-unmodified-since"),
10);
let serverModified = this.modified;
if (serverModified > requestModified) {
code = 412;
status = "Precondition Failed";
sendResponse();
return;
}
@ -411,26 +423,6 @@ StorageServerCollection.prototype = {
}
}
if (options.index_above) {
if (bso.sortindex === undefined) {
return false;
}
if (bso.sortindex <= options.index_above) {
return false;
}
}
if (options.index_below) {
if (bso.sortindex === undefined) {
return false;
}
if (bso.sortindex >= options.index_below) {
return false;
}
}
return true;
},
@ -612,10 +604,11 @@ StorageServerCollection.prototype = {
continue;
}
chunk = chunk.split("=");
let key = decodeURIComponent(chunk[0]);
if (chunk.length == 1) {
options[chunk[0]] = "";
options[key] = "";
} else {
options[chunk[0]] = chunk[1];
options[key] = decodeURIComponent(chunk[1]);
}
}
@ -641,18 +634,6 @@ StorageServerCollection.prototype = {
options.older = parseInt(options.older, 10);
}
if (options.index_above) {
if (!isInteger(options.index_above)) {
throw HTTP_400;
}
}
if (options.index_below) {
if (!isInteger(options.index_below)) {
throw HTTP_400;
}
}
if (options.limit) {
if (!isInteger(options.limit)) {
throw HTTP_400;
@ -683,6 +664,16 @@ StorageServerCollection.prototype = {
response.setStatusLine(request.httpVersion, 304, "Not Modified");
return;
}
} else if (request.hasHeader("x-if-unmodified-since")) {
let requestModified = parseInt(request.getHeader("x-if-unmodified-since"),
10);
let serverModified = this.timestamp;
if (serverModified > requestModified) {
response.setHeader("X-Last-Modified", "" + serverModified);
response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
return;
}
}
if (options.full) {
@ -710,9 +701,7 @@ StorageServerCollection.prototype = {
if (newlines) {
response.setHeader("Content-Type", "application/newlines", false);
let normalized = data.map(function map(d) {
let result = JSON.stringify(d);
return result.replace("\n", "\\u000a");
return JSON.stringify(d);
});
body = normalized.join("\n") + "\n";
@ -755,10 +744,9 @@ StorageServerCollection.prototype = {
}
} else if (inputMediaType == "application/newlines") {
for each (let line in inputBody.split("\n")) {
let json = line.replace("\\u000a", "\n");
let record;
try {
record = JSON.parse(json);
record = JSON.parse(line);
} catch (ex) {
this._log.info("JSON parse error on line!");
return sendMozSvcError(request, response, "8");

View File

@ -870,9 +870,12 @@ StorageServiceRequest.prototype = {
return;
}
if (response.status == 503) {
if (response.status >= 500 && response.status <= 599) {
this._log.error(response.status + " seen from server!");
this._error = new StorageServiceRequestError();
this._error.server = new Error("503 Received.");
this._error.server = new Error(response.status + " status code.");
callOnComplete();
return;
}
callOnComplete();
@ -993,20 +996,6 @@ StorageCollectionGetRequest.prototype = {
}
},
/**
* Only retrieve BSOs whose sortindex is higher than this integer value.
*/
set index_above(value) {
this._namedArgs.index_above = value;
},
/**
* Only retrieve BSOs whose sortindex is lower than this integer value.
*/
set index_below(value) {
this._namedArgs.index_below = value;
},
/**
* Limit the max number of returned BSOs to this integer number.
*/
@ -1084,15 +1073,21 @@ StorageCollectionGetRequest.prototype = {
function StorageCollectionSetRequest() {
StorageServiceRequest.call(this);
this._lines = [];
this._size = 0;
this.size = 0;
this.successfulIDs = new Set();
this.failures = new Map();
// TODO Bug 775781 convert to Set and Map once iterable.
this.successfulIDs = [];
this.failures = {};
this._lines = [];
}
StorageCollectionSetRequest.prototype = {
__proto__: StorageServiceRequest.prototype,
get count() {
return this._lines.length;
},
/**
* Add a BasicStorageObject to this request.
*
@ -1112,33 +1107,384 @@ StorageCollectionSetRequest.prototype = {
throw new Error("Passed BSO must have id defined.");
}
let line = JSON.stringify(bso).replace("\n", "\u000a");
this.addLine(JSON.stringify(bso));
},
/**
* Add a BSO (represented by its serialized newline-delimited form).
*
* You probably shouldn't use this. It is used for batching.
*/
addLine: function addLine(line) {
// This is off by 1 in the larger direction. We don't care.
this._size += line.length + "\n".length;
this.size += line.length + 1;
this._lines.push(line);
},
_onDispatch: function _onDispatch() {
this._data = this._lines.join("\n");
this.size = this._data.length;
},
_completeParser: function _completeParser(response) {
let result = JSON.parse(response.body);
for (let id of result.success) {
this.successfulIDs.add(id);
this.successfulIDs.push(id);
}
this.allSucceeded = true;
for (let [id, reasons] in result.failed) {
for (let [id, reasons] in Iterator(result.failed)) {
this.failures[id] = reasons;
this.allSucceeded = false;
}
},
};
/**
* Represents a batch upload of BSOs to an individual collection.
*
* This is a more intelligent way to upload may BSOs to the server. It will
* split the uploaded data into multiple requests so size limits, etc aren't
* exceeded.
*
* Once a client obtains an instance of this type, it calls `addBSO` for each
* BSO to be uploaded. When the client is done providing BSOs to be uploaded,
* it calls `finish`. When `finish` is called, no more BSOs can be added to the
* batch. When all requests created from this batch have finished, the callback
* provided to `finish` will be invoked.
*
* Clients can also explicitly flush pending outgoing BSOs via `flush`. This
* allows callers to control their own batching/chunking.
*
* Interally, this maintains a queue of StorageCollectionSetRequest to be
* issued. At most one request is allowed to be in-flight at once. This is to
* avoid potential conflicts on the server. And, in the case of conditional
* requests, it prevents requests from being declined due to the server being
* updated by another request issued by us.
*
* If a request errors for any reason, all queued uploads are abandoned and the
* `finish` callback is invoked as soon as possible. The `successfulIDs` and
* `failures` properties will contain data from all requests that had this
* response data. In other words, the IDs have BSOs that were never sent to the
* server are not lumped in to either property.
*
* Requests can be made conditional by setting `locallyModifiedVersion` to the
* most recent version of server data. As responses from the server are seen,
* the last server version is carried forward to subsequent requests.
*
* The server version from the last request is available in the
* `serverModifiedVersion` property. It should only be accessed during or
* after the callback passed to `finish`.
*
* @param client
* (StorageServiceClient) Client instance to use for uploading.
*
* @param collection
* (string) Collection the batch operation will upload to.
*/
function StorageCollectionBatchedSet(client, collection) {
this.client = client;
this.collection = collection;
this._log = client._log;
this.locallyModifiedVersion = null;
this.serverModifiedVersion = null;
// TODO Bug 775781 convert to Set and Map once iterable.
this.successfulIDs = [];
this.failures = {};
// Request currently being populated.
this._stagingRequest = client.setBSOs(this.collection);
// Requests ready to be sent over the wire.
this._outgoingRequests = [];
// Whether we are waiting for a response.
this._requestInFlight = false;
this._onFinishCallback = null;
this._finished = false;
this._errorEncountered = false;
}
StorageCollectionBatchedSet.prototype = {
/**
* Add a BSO to be uploaded as part of this batch.
*/
addBSO: function addBSO(bso) {
if (this._errorEncountered) {
return;
}
let line = JSON.stringify(bso);
if (line.length > this.client.REQUEST_SIZE_LIMIT) {
throw new Error("BSO is larger than allowed limit: " + line.length +
" > " + this.client.REQUEST_SIZE_LIMIT);
}
if (this._stagingRequest.size + line.length > this.client.REQUEST_SIZE_LIMIT) {
this._log.debug("Sending request because payload size would be exceeded");
this._finishStagedRequest();
this._stagingRequest.addLine(line);
return;
}
// We are guaranteed to fit within size limits.
this._stagingRequest.addLine(line);
if (this._stagingRequest.count >= this.client.REQUEST_BSO_COUNT_LIMIT) {
this._log.debug("Sending request because BSO count threshold reached.");
this._finishStagedRequest();
return;
}
},
finish: function finish(cb) {
if (this._finished) {
throw new Error("Batch request has already been finished.");
}
this.flush();
this._onFinishCallback = cb;
this._finished = true;
this._stagingRequest = null;
},
flush: function flush() {
if (this._finished) {
throw new Error("Batch request has been finished.");
}
if (!this._stagingRequest.count) {
return;
}
this._finishStagedRequest();
},
_finishStagedRequest: function _finishStagedRequest() {
this._outgoingRequests.push(this._stagingRequest);
this._sendOutgoingRequest();
this._stagingRequest = this.client.setBSOs(this.collection);
},
_sendOutgoingRequest: function _sendOutgoingRequest() {
if (this._requestInFlight || this._errorEncountered) {
return;
}
if (!this._outgoingRequests.length) {
return;
}
let request = this._outgoingRequests.shift();
if (this.locallyModifiedVersion) {
request.locallyModifiedVersion = this.locallyModifiedVersion;
}
request.dispatch(this._onBatchComplete.bind(this));
this._requestInFlight = true;
},
_onBatchComplete: function _onBatchComplete(error, request) {
this._requestInFlight = false;
this.serverModifiedVersion = request.serverTime;
// Only update if we had a value before. Otherwise, this breaks
// unconditional requests!
if (this.locallyModifiedVersion) {
this.locallyModifiedVersion = request.serverTime;
}
for (let id of request.successfulIDs) {
this.successfulIDs.push(id);
}
for (let [id, reason] in Iterator(request.failures)) {
this.failures[id] = reason;
}
if (request.error) {
this._errorEncountered = true;
}
this._checkFinish();
},
_checkFinish: function _checkFinish() {
if (this._outgoingRequests.length && !this._errorEncountered) {
this._sendOutgoingRequest();
return;
}
if (!this._onFinishCallback) {
return;
}
try {
this._onFinishCallback(this);
} catch (ex) {
this._log.warn("Exception when calling finished callback: " +
CommonUtils.exceptionStr(ex));
}
},
};
Object.freeze(StorageCollectionBatchedSet.prototype);
/**
* Manages a batch of BSO deletion requests.
*
* A single instance of this virtual request allows deletion of many individual
* BSOs without having to worry about server limits.
*
* Instances are obtained by calling `deleteBSOsBatching` on
* StorageServiceClient.
*
* Usage is roughly the same as StorageCollectionBatchedSet. Callers obtain
* an instance and select individual BSOs for deletion by calling `addID`.
* When the caller is finished marking BSOs for deletion, they call `finish`
* with a callback which will be invoked when all deletion requests finish.
*
* When the finished callback is invoked, any encountered errors will be stored
* in the `errors` property of this instance (which is passed to the callback).
* This will be an empty array if no errors were encountered. Else, it will
* contain the errors from the `onComplete` handler of request instances. The
* set of succeeded and failed IDs is not currently available.
*
* Deletes can be made conditional by setting `locallyModifiedVersion`. The
* behavior is the same as request types. The only difference is that the
* updated version from the server as a result of requests is carried forward
* to subsequent requests.
*
* The server version from the last request is stored in the
* `serverModifiedVersion` property. It is not safe to access this until the
* callback from `finish`.
*
* Like StorageCollectionBatchedSet, requests are issued serially to avoid
* race conditions on the server.
*
* @param client
* (StorageServiceClient) Client request is associated with.
* @param collection
* (string) Collection being operated on.
*/
function StorageCollectionBatchedDelete(client, collection) {
this.client = client;
this.collection = collection;
this._log = client._log;
this.locallyModifiedVersion = null;
this.serverModifiedVersion = null;
this.errors = [];
this._pendingIDs = [];
this._requestInFlight = false;
this._finished = false;
this._finishedCallback = null;
}
StorageCollectionBatchedDelete.prototype = {
addID: function addID(id) {
if (this._finished) {
throw new Error("Cannot add IDs to a finished instance.");
}
// If we saw errors already, don't do any work. This is an optimization
// and isn't strictly required, as _sendRequest() should no-op.
if (this.errors.length) {
return;
}
this._pendingIDs.push(id);
if (this._pendingIDs.length >= this.client.REQUEST_BSO_DELETE_LIMIT) {
this._sendRequest();
}
},
/**
* Finish this batch operation.
*
* No more IDs can be added to this operation. Existing IDs are flushed as
* a request. The passed callback will be called when all requests have
* finished.
*/
finish: function finish(cb) {
if (this._finished) {
throw new Error("Batch delete instance has already been finished.");
}
this._finished = true;
this._finishedCallback = cb;
if (this._pendingIDs.length) {
this._sendRequest();
}
},
_sendRequest: function _sendRequest() {
// Only allow 1 active request at a time and don't send additional
// requests if one has failed.
if (this._requestInFlight || this.errors.length) {
return;
}
let ids = this._pendingIDs.splice(0, this.client.REQUEST_BSO_DELETE_LIMIT);
let request = this.client.deleteBSOs(this.collection, ids);
if (this.locallyModifiedVersion) {
request.locallyModifiedVersion = this.locallyModifiedVersion;
}
request.dispatch(this._onRequestComplete.bind(this));
this._requestInFlight = true;
},
_onRequestComplete: function _onRequestComplete(error, request) {
this._requestInFlight = false;
if (error) {
// We don't currently track metadata of what failed. This is an obvious
// feature that could be added.
this._log.warn("Error received from server: " + error);
this.errors.push(error);
}
this.serverModifiedVersion = request.serverTime;
// If performing conditional requests, carry forward the new server version
// so subsequent conditional requests work.
if (this.locallyModifiedVersion) {
this.locallyModifiedVersion = request.serverTime;
}
if (this._pendingIDs.length && !this.errors.length) {
this._sendRequest();
return;
}
if (!this._finishedCallback) {
return;
}
try {
this._finishedCallback(this);
} catch (ex) {
this._log.warn("Exception when invoking finished callback: " +
CommonUtils.exceptionStr(ex));
}
},
};
Object.freeze(StorageCollectionBatchedDelete.prototype);
/**
* Construct a new client for the SyncStorage API, version 2.0.
*
@ -1195,6 +1541,27 @@ StorageServiceClient.prototype = {
*/
userAgent: "StorageServiceClient",
/**
* Maximum size of entity bodies.
*
* TODO this should come from the server somehow. See bug 769759.
*/
REQUEST_SIZE_LIMIT: 512000,
/**
* Maximum number of BSOs in requests.
*
* TODO this should come from the server somehow. See bug 769759.
*/
REQUEST_BSO_COUNT_LIMIT: 100,
/**
* Maximum number of BSOs that can be deleted in a single DELETE.
*
* TODO this should come from the server. See bug 769759.
*/
REQUEST_BSO_DELETE_LIMIT: 100,
_baseURI: null,
_log: null,
@ -1584,11 +1951,9 @@ StorageServiceClient.prototype = {
* has additional functions and properties specific to this operation. See
* its documentation for more.
*
* Future improvement: support streaming of uploaded records. Currently, data
* is buffered in the client before going over the wire. Ideally, we'd support
* sending over the wire as soon as data is available. This will require
* support in RESTRequest, which doesn't support streaming on requests, only
* responses.
* Most consumers interested in submitting multiple BSOs to the server will
* want to use `setBSOsBatching` instead. That API intelligently splits up
* requests as necessary, etc.
*
* Example usage:
*
@ -1635,6 +2000,30 @@ StorageServiceClient.prototype = {
return request;
},
/**
* This is a batching variant of setBSOs.
*
* Whereas `setBSOs` is a 1:1 mapping between function calls and HTTP
* requests issued, this one is a 1:N mapping. It will intelligently break
* up outgoing BSOs into multiple requests so size limits, etc aren't
* exceeded.
*
* Please see the documentation for `StorageCollectionBatchedSet` for
* usage info.
*
* @param collection
* (string) Collection to operate on.
* @return
* (StorageCollectionBatchedSet) Batched set instance.
*/
setBSOsBatching: function setBSOsBatching(collection) {
if (!collection) {
throw new Error("collection argument must be defined.");
}
return new StorageCollectionBatchedSet(this, collection);
},
/**
* Deletes a single BSO from a collection.
*
@ -1670,6 +2059,10 @@ StorageServiceClient.prototype = {
* The request can be made conditional by setting `locallyModifiedVersion`
* on the returned request instance.
*
* If the number of BSOs to delete is potentially large, it is preferred to
* use `deleteBSOsBatching`. That API automatically splits the operation into
* multiple requests so server limits aren't exceeded.
*
* @param collection
* (string) Name of collection to delete BSOs from.
* @param ids
@ -1688,6 +2081,24 @@ StorageServiceClient.prototype = {
});
},
/**
* Bulk deletion of BSOs with no size limit.
*
* This allows a large amount of BSOs to be deleted easily. It will formulate
* multiple `deleteBSOs` queries so the client does not exceed server limits.
*
* @param collection
* (string) Name of collection to delete BSOs from.
* @return StorageCollectionBatchedDelete
*/
deleteBSOsBatching: function deleteBSOsBatching(collection) {
if (!collection) {
throw new Error("collection argument must be defined.");
}
return new StorageCollectionBatchedDelete(this, collection);
},
/**
* Deletes a single collection from the server.
*

View File

@ -253,6 +253,28 @@ add_test(function test_bso_get_existing() {
server.stop(run_next_test);
});
add_test(function test_percent_decoding() {
_("Ensure query string arguments with percent encoded are handled.");
let server = new StorageServer();
server.registerUser("123", "password");
server.startSynchronous(PORT);
let coll = server.user("123").createCollection("test");
coll.insert("001", {foo: "bar"});
coll.insert("002", {bar: "foo"});
let request = localRequest("/2.0/123/storage/test?ids=001%2C002", "123",
"password");
let error = doGetRequest(request);
do_check_null(error);
do_check_eq(request.response.status, 200);
let items = JSON.parse(request.response.body).items;
do_check_attribute_count(items, 2);
server.stop(run_next_test);
});
add_test(function test_bso_404() {
_("Ensure the server responds with a 404 if a BSO does not exist.");
@ -442,6 +464,60 @@ add_test(function test_bso_delete_unmodified() {
server.stop(run_next_test);
});
add_test(function test_collection_get_unmodified_since() {
_("Ensure conditional unmodified get on collection works when it should.");
let server = new StorageServer();
server.registerUser("123", "password");
server.startSynchronous(PORT);
let collection = server.user("123").createCollection("testcoll");
collection.insert("bso0", {foo: "bar"});
let serverModified = collection.timestamp;
let request1 = localRequest("/2.0/123/storage/testcoll", "123", "password");
request1.setHeader("X-If-Unmodified-Since", serverModified);
let error = doGetRequest(request1);
do_check_null(error);
do_check_eq(request1.response.status, 200);
let request2 = localRequest("/2.0/123/storage/testcoll", "123", "password");
request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
let error = doGetRequest(request2);
do_check_null(error);
do_check_eq(request2.response.status, 412);
server.stop(run_next_test);
});
add_test(function test_bso_get_unmodified_since() {
_("Ensure conditional unmodified get on BSO works appropriately.");
let server = new StorageServer();
server.registerUser("123", "password");
server.startSynchronous(PORT);
let collection = server.user("123").createCollection("testcoll");
let bso = collection.insert("bso0", {foo: "bar"});
let serverModified = bso.modified;
let request1 = localRequest("/2.0/123/storage/testcoll/bso0", "123",
"password");
request1.setHeader("X-If-Unmodified-Since", serverModified);
let error = doGetRequest(request1);
do_check_null(error);
do_check_eq(request1.response.status, 200);
let request2 = localRequest("/2.0/123/storage/testcoll/bso0", "123",
"password");
request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
let error = doGetRequest(request2);
do_check_null(error);
do_check_eq(request2.response.status, 412);
server.stop(run_next_test);
});
add_test(function test_missing_collection_404() {
_("Ensure a missing collection returns a 404.");

View File

@ -12,7 +12,11 @@ function run_test() {
run_next_test();
}
function getEmptyServer(user="765", password="password") {
function getRandomUser() {
return "" + (Math.floor(Math.random() * 100000) + 1);
}
function getEmptyServer(user=getRandomUser(), password="password") {
let users = {};
users[user] = password;
@ -23,7 +27,7 @@ function getEmptyServer(user="765", password="password") {
});
}
function getClient(user="765", password="password") {
function getClient(user=getRandomUser(), password="password") {
let client = new StorageServiceClient(BASE_URI + "/" + user);
client.addListener({
onDispatch: function onDispatch(request) {
@ -35,7 +39,7 @@ function getClient(user="765", password="password") {
return client;
}
function getServerAndClient(user="765", password="password") {
function getServerAndClient(user=getRandomUser(), password="password") {
let server = getEmptyServer(user, password);
let client = getClient(user, password);
@ -644,9 +648,9 @@ add_test(function test_set_bsos_simple() {
do_check_null(error);
let successful = req.successfulIDs;
do_check_eq(successful.size(), 2);
do_check_true(successful.has(bso0.id));
do_check_true(successful.has(bso1.id));
do_check_eq(successful.length, 2);
do_check_eq(successful.indexOf(bso0.id), 0);
do_check_true(successful.indexOf(bso1.id), 1);
server.stop(run_next_test);
});
@ -701,7 +705,7 @@ add_test(function test_set_bsos_newline() {
request.dispatch(function onComplete(error, request) {
do_check_null(error);
do_check_eq(request.successfulIDs.size(), 2);
do_check_eq(request.successfulIDs.length, 2);
let coll = user.collection("testcoll");
do_check_eq(coll.bso("bso0").payload, bso0.payload);
@ -965,3 +969,410 @@ add_test(function test_network_error_listener() {
run_next_test();
});
});
add_test(function test_batching_set_too_large() {
_("Ensure we throw when attempting to add a BSO that is too large to fit.");
let [server, client, username] = getServerAndClient();
let request = client.setBSOsBatching("testcoll");
let payload = "";
// The actual length of the payload is a little less. But, this ensures we
// exceed it.
for (let i = 0; i < client.REQUEST_SIZE_LIMIT; i++) {
payload += i;
}
let bso = new BasicStorageObject("bso");
bso.payload = payload;
do_check_throws(function add() { request.addBSO(bso); });
server.stop(run_next_test);
});
add_test(function test_batching_set_basic() {
_("Ensure batching set works with single requests.");
let [server, client, username] = getServerAndClient();
let request = client.setBSOsBatching("testcoll");
for (let i = 0; i < 10; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "payload" + i;
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(request.successfulIDs.length, 10);
let collection = server.user(username).collection("testcoll");
do_check_eq(collection.timestamp, request.serverModifiedVersion);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_batch_count() {
_("Ensure multiple outgoing request batching works when count is exceeded.");
let [server, client, username] = getServerAndClient();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let request = client.setBSOsBatching("testcoll");
for (let i = 1; i <= 300; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "XXXXXXX";
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(request.successfulIDs.length, 300);
do_check_eq(requestCount, 3);
let collection = server.user(username).collection("testcoll");
do_check_eq(collection.timestamp, request.serverModifiedVersion);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_batch_size() {
_("Ensure outgoing requests batch when size is exceeded.");
let [server, client, username] = getServerAndClient();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
};
let limit = client.REQUEST_SIZE_LIMIT;
let request = client.setBSOsBatching("testcoll");
// JavaScript: Y U NO EASY REPETITION FUNCTIONALITY?
let data = [];
for (let i = (limit / 2) - 100; i; i -= 1) {
data.push("X");
}
let payload = data.join("");
for (let i = 0; i < 4; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = payload;
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(request.successfulIDs.length, 4);
do_check_eq(requestCount, 2);
let collection = server.user(username).collection("testcoll");
do_check_eq(collection.timestamp, request.serverModifiedVersion);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_flush() {
_("Ensure flushing batch sets works.");
let [server, client, username] = getServerAndClient();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let request = client.setBSOsBatching("testcoll");
for (let i = 1; i < 101; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "foo";
request.addBSO(bso);
if (i % 10 == 0) {
request.flush();
}
}
request.finish(function onFinish(request) {
do_check_eq(request.successfulIDs.length, 100);
do_check_eq(requestCount, 10);
let collection = server.user(username).collection("testcoll");
do_check_eq(collection.timestamp, request.serverModifiedVersion);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_conditional_success() {
_("Ensure conditional requests for batched sets work properly.");
let [server, client, username] = getServerAndClient();
let collection = server.user(username).createCollection("testcoll");
let lastServerVersion = Date.now();
collection.insertBSO(new ServerBSO("foo", "bar", lastServerVersion));
collection.timestamp = lastServerVersion;
do_check_eq(collection.timestamp, lastServerVersion);
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let request = client.setBSOsBatching("testcoll");
request.locallyModifiedVersion = collection.timestamp;
for (let i = 1; i < 251; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "foo" + i;
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 3);
do_check_eq(collection.timestamp, request.serverModifiedVersion);
do_check_eq(collection.timestamp, request.locallyModifiedVersion);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_initial_failure() {
_("Ensure that an initial request failure setting BSOs is handled properly.");
let [server, client, username] = getServerAndClient();
let collection = server.user(username).createCollection("testcoll");
collection.timestamp = Date.now();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let request = client.setBSOsBatching("testcoll");
request.locallyModifiedVersion = collection.timestamp - 1;
for (let i = 1; i < 250; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "foo" + i;
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 1);
do_check_eq(request.successfulIDs.length, 0);
do_check_eq(Object.keys(request.failures).length, 0);
server.stop(run_next_test);
});
});
add_test(function test_batching_set_subsequent_failure() {
_("Ensure a non-initial failure during batching set is handled properly.");
let [server, client, username] = getServerAndClient();
let collection = server.user(username).createCollection("testcoll");
collection.timestamp = Date.now();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
if (requestCount == 1) {
return;
}
collection.timestamp++;
}
let request = client.setBSOsBatching("testcoll");
request.locallyModifiedVersion = collection.timestamp;
for (let i = 0; i < 250; i++) {
let bso = new BasicStorageObject("bso" + i);
bso.payload = "foo" + i;
request.addBSO(bso);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 2);
do_check_eq(request.successfulIDs.length, 100);
do_check_eq(Object.keys(request.failures).length, 0);
server.stop(run_next_test);
});
});
function getBatchedDeleteData(collection="testcoll") {
let [server, client, username] = getServerAndClient();
let serverBSOs = {};
for (let i = 1000; i; i -= 1) {
serverBSOs["bso" + i] = new ServerBSO("bso" + i, "payload" + i);
}
let user = server.user(username);
user.createCollection(collection, serverBSOs);
return [server, client, username, collection];
}
add_test(function test_batched_delete_single() {
_("Ensure batched delete with single request works.");
let [server, client, username, collection] = getBatchedDeleteData();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount += 1;
}
let request = client.deleteBSOsBatching(collection);
for (let i = 1; i < 51; i += 1) {
request.addID("bso" + i);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 1);
do_check_eq(request.errors.length, 0);
let coll = server.user(username).collection(collection);
do_check_eq(coll.count(), 950);
do_check_eq(request.serverModifiedVersion, coll.timestamp);
server.stop(run_next_test);
});
});
add_test(function test_batched_delete_multiple() {
_("Ensure batched delete splits requests properly.");
let [server, client, username, collection] = getBatchedDeleteData();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount += 1;
}
let request = client.deleteBSOsBatching(collection);
for (let i = 1; i < 251; i += 1) {
request.addID("bso" + i);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 3);
do_check_eq(request.errors.length, 0);
let coll = server.user(username).collection(collection);
do_check_eq(coll.count(), 750);
do_check_eq(request.serverModifiedVersion, coll.timestamp);
server.stop(run_next_test);
});
});
add_test(function test_batched_delete_conditional_success() {
_("Ensure conditional batched delete all work.");
let [server, client, username, collection] = getBatchedDeleteData();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let serverCollection = server.user(username).collection(collection);
let initialTimestamp = serverCollection.timestamp;
let request = client.deleteBSOsBatching(collection);
request.locallyModifiedVersion = initialTimestamp;
for (let i = 1; i < 251; i += 1) {
request.addID("bso" + 1);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 3);
do_check_eq(request.errors.length, 0);
do_check_true(request.locallyModifiedVersion > initialTimestamp);
server.stop(run_next_test);
});
});
add_test(function test_batched_delete_conditional_initial_failure() {
_("Ensure conditional batched delete failure on initial request works.");
// The client needs to issue multiple requests but the first one was
// rejected. The client should only issue that initial request.
let [server, client, username, collection] = getBatchedDeleteData();
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
}
let serverCollection = server.user(username).collection(collection);
let request = client.deleteBSOsBatching(collection);
request.locallyModifiedVersion = serverCollection.timestamp - 1;
for (let i = 1; i < 251; i += 1) {
request.addID("bso" + i);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 1);
do_check_eq(request.errors.length, 1);
server.stop(run_next_test);
});
});
add_test(function test_batched_delete_conditional_subsequent_failure() {
_("Ensure conditional batched delete failure on non-initial request.");
let [server, client, username, collection] = getBatchedDeleteData();
let serverCollection = server.user(username).collection(collection);
let requestCount = 0;
server.callback.onRequest = function onRequest() {
requestCount++;
if (requestCount <= 1) {
return;
}
// Advance collection's timestamp on subsequent requests so request is
// rejected.
serverCollection.timestamp++;
}
let request = client.deleteBSOsBatching(collection);
request.locallyModifiedVersion = serverCollection.timestamp;
for (let i = 1; i < 251; i += 1) {
request.addID("bso" + i);
}
request.finish(function onFinish(request) {
do_check_eq(requestCount, 2);
do_check_eq(request.errors.length, 1);
server.stop(run_next_test);
});
});

View File

@ -128,20 +128,27 @@ BrowserRootActor.prototype = {
* tab closures.
*/
watchWindow: function BRA_watchWindow(aWindow) {
aWindow.getBrowser().tabContainer.addEventListener("TabClose",
this.onTabClosed,
false);
this.getTabContainer(aWindow).addEventListener("TabClose",
this.onTabClosed,
false);
},
/**
* Stop watching a window for tab closes.
*/
unwatchWindow: function BRA_unwatchWindow(aWindow) {
aWindow.getBrowser().tabContainer.removeEventListener("TabClose",
this.onTabClosed);
this.getTabContainer(aWindow).removeEventListener("TabClose",
this.onTabClosed);
this.exitTabActor(aWindow);
},
/**
* Return the tab container for the specified window.
*/
getTabContainer: function BRA_getTabContainer(aWindow) {
return aWindow.getBrowser().tabContainer;
},
/**
* When a tab is closed, exit its tab actor. The actor
* will be dropped at the next listTabs request.

View File

@ -6807,10 +6807,10 @@ nsWindow::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, PRUint32 aModifie
}
}
static BOOL WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam)
{
*((HWND*)lParam) = hwnd;
return FALSE;
static BOOL WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam)
{
*((HWND*)lParam) = hwnd;
return FALSE;
}
static void InvalidatePluginAsWorkaround(nsWindow *aWindow, const nsIntRect &aRect)