Merge from mozilla-inbound.

This commit is contained in:
Jan de Mooij 2013-03-15 10:32:44 +01:00
commit 89bdac61f0
165 changed files with 3414 additions and 1596 deletions

View File

@ -409,7 +409,7 @@ nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible)
uint32_t childCount = aAccessible->ChildCount();
for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
Accessible* child = aAccessible->GetChildAt(childIdx);
if (IsText(child)) {
if (!IsEmbeddedObject(child)) {
foundText = true;
break;
}
@ -429,7 +429,7 @@ nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible)
uint32_t
nsAccUtils::TextLength(Accessible* aAccessible)
{
if (!IsText(aAccessible))
if (IsEmbeddedObject(aAccessible))
return 1;
TextLeafAccessible* textLeaf = aAccessible->AsTextLeaf();

View File

@ -232,16 +232,6 @@ public:
static bool IsTextInterfaceSupportCorrect(Accessible* aAccessible);
#endif
/**
* Return true if the given accessible has text role.
*/
static bool IsText(nsIAccessible *aAcc)
{
uint32_t role = Role(aAcc);
return role == nsIAccessibleRole::ROLE_TEXT_LEAF ||
role == nsIAccessibleRole::ROLE_STATICTEXT;
}
/**
* Return text length of the given accessible, return 0 on failure.
*/

View File

@ -68,10 +68,6 @@ public:
{
return GetNode() && GetNode()->IsNodeOfType(nsINode::eCONTENT);
}
bool IsDocumentNode() const
{
return GetNode() && GetNode()->IsNodeOfType(nsINode::eDOCUMENT);
}
/**
* Return the unique identifier of the accessible.

View File

@ -265,7 +265,7 @@ nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
}
//XXX: is it necessary to care the accessible is not a document?
if (aAccessible->IsDocumentNode())
if (aAccessible->IsDoc())
return NS_ERROR_UNEXPECTED;
nsIContent *content = aAccessible->GetContent();

View File

@ -2671,7 +2671,7 @@ Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
mChildren[idx]->mIndexInParent = idx;
}
if (nsAccUtils::IsText(aChild))
if (!nsAccUtils::IsEmbeddedObject(aChild))
SetChildrenFlag(eMixedChildren);
mEmbeddedObjCollector = nullptr;
@ -3038,7 +3038,7 @@ Accessible::ContainerWidget() const
}
// Don't cross DOM document boundaries.
if (parent->IsDocumentNode())
if (parent->IsDoc())
break;
}
}

View File

@ -266,7 +266,7 @@ HyperTextAccessible::GetPosAndText(int32_t& aStartOffset, int32_t& aEndOffset,
}
nsIFrame *primaryFrame = frame;
endFrame = frame;
if (nsAccUtils::IsText(childAcc)) {
if (!nsAccUtils::IsEmbeddedObject(childAcc)) {
// We only need info up to rendered offset -- that is what we're
// converting to content offset
int32_t substringEndOffset = -1;
@ -708,14 +708,12 @@ HyperTextAccessible::GetRelativeOffset(nsIPresShell* aPresShell,
nsresult rv;
int32_t contentOffset = aFromOffset;
if (nsAccUtils::IsText(aFromAccessible)) {
nsIFrame *frame = aFromAccessible->GetFrame();
NS_ENSURE_TRUE(frame, -1);
nsIFrame *frame = aFromAccessible->GetFrame();
NS_ENSURE_TRUE(frame, -1);
if (frame->GetType() == nsGkAtoms::textFrame) {
rv = RenderedToContentOffset(frame, aFromOffset, &contentOffset);
NS_ENSURE_SUCCESS(rv, -1);
}
if (frame->GetType() == nsGkAtoms::textFrame) {
rv = RenderedToContentOffset(frame, aFromOffset, &contentOffset);
NS_ENSURE_SUCCESS(rv, -1);
}
nsPeekOffsetStruct pos(aAmount, aDirection, contentOffset,

View File

@ -16,7 +16,7 @@
function doTest()
{
SimpleTest.expectAssertions(60);
SimpleTest.expectAssertions(52);
// __o__n__e__w__o__r__d__\n
// 0 1 2 3 4 5 6 7

View File

@ -12,9 +12,9 @@
src="../text.js"></script>
<script type="application/javascript">
if (navigator.platform.startsWith("Mac")) {
SimpleTest.expectAssertions(0, 23);
SimpleTest.expectAssertions(0, 19);
} else {
SimpleTest.expectAssertions(23);
SimpleTest.expectAssertions(19);
}
function doTest()

View File

@ -14,9 +14,9 @@
src="../text.js"></script>
<script type="application/javascript">
if (navigator.platform.startsWith("Mac")) {
SimpleTest.expectAssertions(0, 10);
SimpleTest.expectAssertions(0, 8);
} else {
SimpleTest.expectAssertions(10);
SimpleTest.expectAssertions(8);
}
function doTest()

View File

@ -659,3 +659,6 @@ pref("dom.disable_input_file", true);
pref("general.useragent.enable_overrides", true);
pref("b2g.version", @MOZ_B2G_VERSION@);
// Disable console buffering to save memory.
pref("consoleservice.buffered", false);

View File

@ -89,6 +89,7 @@ function doInternalWatch() {
},
JSON.stringify(options),
function(...things) {
// internal watch log callback
log("(watch) internal: ", things);
}
);
@ -136,7 +137,7 @@ addEventListener("DOMContentLoaded", function(e) {
// listen for request
addMessageListener(kIdentityDelegateRequest, function(aMessage) {
log("injected identity.js received", kIdentityDelegateRequest, "\n\n\n");
log("injected identity.js received", kIdentityDelegateRequest);
options = aMessage.json;
showUI = true;
func = doInternalRequest;
@ -145,7 +146,7 @@ addMessageListener(kIdentityDelegateRequest, function(aMessage) {
// listen for watch
addMessageListener(kIdentityDelegateWatch, function(aMessage) {
log("injected identity.js received", kIdentityDelegateWatch, "\n\n\n");
log("injected identity.js received", kIdentityDelegateWatch);
options = aMessage.json;
showUI = false;
func = doInternalWatch;
@ -154,7 +155,7 @@ addMessageListener(kIdentityDelegateWatch, function(aMessage) {
// listen for logout
addMessageListener(kIdentityDelegateLogout, function(aMessage) {
log("injected identity.js received", kIdentityDelegateLogout, "\n\n\n");
log("injected identity.js received", kIdentityDelegateLogout);
options = aMessage.json;
showUI = false;
func = doInternalLogout;

View File

@ -92,7 +92,7 @@ addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
requestId = aMessage.json.requestId;
});
addEventListener("DOMContentLoaded", function(e) {
addEventListener("DOMWindowCreated", function(e) {
content.wrappedJSObject.paymentSuccess = paymentSuccess;
content.wrappedJSObject.paymentFailed = paymentFailed;
});

View File

@ -25,7 +25,7 @@
*
* In order for navigator.id.request() to maintain state in a single
* cookie jar, we cause all Persona interactions to take place in a
* gaia context that is launched by the system application, with the
* content context that is launched by the system application, with the
* result that Persona has a single cookie jar that all Relying
* Parties can use. Since of course those Relying Parties cannot
* reach into the system cookie jar, the Controller in this module
@ -43,10 +43,10 @@
* requesting Persona functions (doWatch, doReady, doLogout).
*
* The Identity service sends these observer messages to the
* Controller in this module, which in turn triggers gaia to open a
* Controller in this module, which in turn triggers content to open a
* window to host the Persona js. If user interaction is required,
* gaia will open the trusty UI. If user interaction is not required,
* and we only need to get to Persona functions, gaia will open a
* content will open the trusty UI. If user interaction is not required,
* and we only need to get to Persona functions, content will open a
* hidden iframe. In either case, a window is opened into which the
* controller causes the script identity.js to be injected. This
* script provides the glue between the in-page javascript and the
@ -89,11 +89,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "Logger",
const kIdentityShimFile = "chrome://browser/content/identity.js";
// Type of MozChromeEvents to handle id dialogs.
const kOpenIdentityDialog = "open-id-dialog";
const kCloseIdentityDialog = "close-id-dialog";
const kOpenIdentityDialog = "id-dialog-open";
const kDoneIdentityDialog = "id-dialog-done";
const kCloseIdentityDialog = "id-dialog-close-iframe";
// Observer messages to communicate to shim
const kReceivedIdentityAssertion = "received-id-assertion";
const kIdentityDelegateWatch = "identity-delegate-watch";
const kIdentityDelegateRequest = "identity-delegate-request";
const kIdentityDelegateLogout = "identity-delegate-logout";
@ -107,12 +107,12 @@ function log(...aMessageArgs) {
}
/*
* GaiaInterface encapsulates the our gaia functions. There are only two:
* ContentInterface encapsulates the our content functions. There are only two:
*
* getContent - return the current content window
* sendChromeEvent - send a chromeEvent from the browser shell
*/
let GaiaInterface = {
let ContentInterface = {
_getBrowser: function SignInToWebsiteController__getBrowser() {
return Services.wm.getMostRecentWindow("navigator:browser");
},
@ -126,40 +126,100 @@ let GaiaInterface = {
}
};
/*
* The Pipe abstracts the communcation channel between the Controller
* and the identity.js code running in the browser window.
*/
let Pipe = {
function Pipe() {
this._watchers = [];
}
/*
* communicate - launch a gaia window with certain options and
* provide a callback for handling messages.
*
* @param aRpOptions options describing the Relying Party's
* (dictionary) call, such as origin and loggedInUser.
*
* @param aGaiaOptions showUI: boolean
* (dictionary) message: name of the message to emit
* (request, logout, watch)
*
* @param aMessageCallback function to call on receipt of a
* (function) do-method message. These messages name
* a method ('login', 'logout', etc.) and
* carry optional parameters. The Pipe does
* not know what the messages mean; it is
* up to the caller to interpret them and
* act on them.
*/
communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
log("open gaia dialog with options:", aGaiaOptions);
Pipe.prototype = {
init: function pipe_init() {
Services.obs.addObserver(this, "identity-child-process-shutdown", false);
Services.obs.addObserver(this, "identity-controller-unwatch", false);
},
uninit: function pipe_init() {
Services.obs.removeObserver(this, "identity-child-process-shutdown");
Services.obs.removeObserver(this, "identity-controller-unwatch");
},
observe: function Pipe_observe(aSubject, aTopic, aData) {
let options = {};
if (aSubject) {
options = aSubject.wrappedJSObject;
}
switch (aTopic) {
case "identity-child-process-shutdown":
log("pipe removing watchers by message manager");
this._removeWatchers(null, options.messageManager);
break;
case "identity-controller-unwatch":
log("unwatching", options.id);
this._removeWatchers(options.id, options.messageManager);
break;
}
},
_addWatcher: function Pipe__addWatcher(aId, aMm) {
log("Adding watcher with id", aId);
for (let i = 0; i < this._watchers.length; ++i) {
let watcher = this._watchers[i];
if (this._watcher.id === aId) {
watcher.count++;
return;
}
}
this._watchers.push({id: aId, count: 1, mm: aMm});
},
_removeWatchers: function Pipe__removeWatcher(aId, aMm) {
let checkId = aId !== null;
let index = -1;
for (let i = 0; i < this._watchers.length; ++i) {
let watcher = this._watchers[i];
if (watcher.mm === aMm &&
(!checkId || (checkId && watcher.id === aId))) {
index = i;
break;
}
}
if (index !== -1) {
if (checkId) {
if (--(this._watchers[index].count) === 0) {
this._watchers.splice(index, 1);
}
} else {
this._watchers.splice(index, 1);
}
}
if (this._watchers.length === 0) {
log("No more watchers; clean up persona host iframe");
let detail = {
type: kCloseIdentityDialog
};
log('telling content to close the dialog');
// tell content to close the dialog
ContentInterface.sendChromeEvent(detail);
}
},
communicate: function(aRpOptions, aContentOptions, aMessageCallback) {
let rpID = aRpOptions.id;
let rpMM = aRpOptions.mm;
if (rpMM) {
this._addWatcher(rpID, rpMM);
}
log("RP options:", aRpOptions, "\n content options:", aContentOptions);
// This content variable is injected into the scope of
// kIdentityShimFile, where it is used to access the BrowserID object
// and its internal API.
let content = GaiaInterface.getContent();
let content = ContentInterface.getContent();
let mm = null;
let uuid = getRandomId();
let self = this;
if (!content) {
log("ERROR: what the what? no content window?");
@ -178,14 +238,14 @@ let Pipe = {
removeMessageListeners();
let detail = {
type: kReceivedIdentityAssertion,
showUI: aGaiaOptions.showUI || false,
id: kReceivedIdentityAssertion + "-" + uuid,
requestId: aRpOptions.id
type: kDoneIdentityDialog,
showUI: aContentOptions.showUI || false,
id: kDoneIdentityDialog + "-" + uuid,
requestId: aRpOptions.id
};
log('telling gaia to close the dialog');
// tell gaia to close the dialog
GaiaInterface.sendChromeEvent(detail);
log('received delegate finished; telling content to close the dialog');
ContentInterface.sendChromeEvent(detail);
self._removeWatchers(rpID, rpMM);
}
content.addEventListener("mozContentEvent", function getAssertion(evt) {
@ -224,11 +284,11 @@ let Pipe = {
mm.addMessageListener(kIdentityControllerDoMethod, aMessageCallback);
mm.addMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
mm.sendAsyncMessage(aGaiaOptions.message, aRpOptions);
mm.sendAsyncMessage(aContentOptions.message, aRpOptions);
}
break;
case kReceivedIdentityAssertion + '-' + uuid:
case kDoneIdentityDialog + '-' + uuid:
// Received our assertion. The message manager callbacks will handle
// communicating back to the IDService. All we have to do is remove
// this listener.
@ -242,27 +302,27 @@ let Pipe = {
});
// Tell gaia to open the identity iframe or trusty popup. The parameter
// showUI signals whether user interaction is needed. If it is, gaia will
// Tell content to open the identity iframe or trusty popup. The parameter
// showUI signals whether user interaction is needed. If it is, content will
// open a dialog; if not, a hidden iframe. In each case, BrowserID is
// available in the context.
let detail = {
type: kOpenIdentityDialog,
showUI: aGaiaOptions.showUI || false,
showUI: aContentOptions.showUI || false,
id: kOpenIdentityDialog + "-" + uuid,
requestId: aRpOptions.id
};
GaiaInterface.sendChromeEvent(detail);
ContentInterface.sendChromeEvent(detail);
}
};
/*
* The controller sits between the IdentityService used by DOMIdentity
* and a gaia process launches an (invisible) iframe or (visible)
* and a content process launches an (invisible) iframe or (visible)
* trusty UI. Using an injected js script (identity.js), the
* controller enables the gaia window to access the persona identity
* controller enables the content window to access the persona identity
* storage in the system cookie jar and send events back via the
* controller into IdentityService and DOM, and ultimately up to the
* Relying Party, which is open in a different window context.
@ -270,12 +330,12 @@ let Pipe = {
this.SignInToWebsiteController = {
/*
* Initialize the controller. To use a different gaia communication pipe,
* Initialize the controller. To use a different content communication pipe,
* such as when mocking it in tests, pass aOptions.pipe.
*/
init: function SignInToWebsiteController_init(aOptions) {
aOptions = aOptions || {};
this.pipe = aOptions.pipe || Pipe;
this.pipe = aOptions.pipe || new Pipe();
Services.obs.addObserver(this, "identity-controller-watch", false);
Services.obs.addObserver(this, "identity-controller-request", false);
Services.obs.addObserver(this, "identity-controller-logout", false);
@ -293,7 +353,7 @@ this.SignInToWebsiteController = {
if (aSubject) {
options = aSubject.wrappedJSObject;
}
switch(aTopic) {
switch (aTopic) {
case "identity-controller-watch":
this.doWatch(options);
break;
@ -320,7 +380,7 @@ this.SignInToWebsiteController = {
message = JSON.parse(message);
}
switch(message.method) {
switch (message.method) {
case "ready":
IdentityService.doReady(aRpId);
break;
@ -350,11 +410,12 @@ this.SignInToWebsiteController = {
doWatch: function SignInToWebsiteController_doWatch(aRpOptions) {
// dom prevents watch from being called twice
var gaiaOptions = {
let contentOptions = {
message: kIdentityDelegateWatch,
showUI: false
};
this.pipe.communicate(aRpOptions, gaiaOptions, this._makeDoMethodCallback(aRpOptions.id));
this.pipe.communicate(aRpOptions, contentOptions,
this._makeDoMethodCallback(aRpOptions.id));
},
/**
@ -362,12 +423,12 @@ this.SignInToWebsiteController = {
*/
doRequest: function SignInToWebsiteController_doRequest(aRpOptions) {
log("doRequest", aRpOptions);
// tell gaia to open the identity popup
var gaiaOptions = {
let contentOptions = {
message: kIdentityDelegateRequest,
showUI: true
};
this.pipe.communicate(aRpOptions, gaiaOptions, this._makeDoMethodCallback(aRpOptions.id));
this.pipe.communicate(aRpOptions, contentOptions,
this._makeDoMethodCallback(aRpOptions.id));
},
/*
@ -375,11 +436,12 @@ this.SignInToWebsiteController = {
*/
doLogout: function SignInToWebsiteController_doLogout(aRpOptions) {
log("doLogout", aRpOptions);
var gaiaOptions = {
let contentOptions = {
message: kIdentityDelegateLogout,
showUI: false
};
this.pipe.communicate(aRpOptions, gaiaOptions, this._makeDoMethodCallback(aRpOptions.id));
this.pipe.communicate(aRpOptions, contentOptions,
this._makeDoMethodCallback(aRpOptions.id));
}
};

View File

@ -1043,7 +1043,7 @@ pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
pref("devtools.toolbox.sideEnabled", false);
pref("devtools.toolbox.sideEnabled", true);
// Enable the Inspector
pref("devtools.inspector.enabled", true);

View File

@ -7198,7 +7198,6 @@ let gPrivateBrowsingUI = {
// temporary fix until bug 463607 is fixed
document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
// Adjust the window's title
if (window.location.href == getBrowserURL()) {
#ifdef XP_MACOSX
if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
@ -7206,6 +7205,7 @@ let gPrivateBrowsingUI = {
}
#endif
// Adjust the window's title
let docElement = document.documentElement;
docElement.setAttribute("title",
docElement.getAttribute("title_privatebrowsing"));
@ -7214,6 +7214,23 @@ let gPrivateBrowsingUI = {
docElement.setAttribute("privatebrowsingmode",
PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
gBrowser.updateTitlebar();
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Adjust the New Window menu entries
[
{ normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
{ normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" },
].forEach(function(menu) {
let newWindow = document.getElementById(menu.normal);
let newPrivateWindow = document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
newPrivateWindow.hidden = true;
newWindow.label = newPrivateWindow.label;
newWindow.accessKey = newPrivateWindow.accessKey;
newWindow.command = newPrivateWindow.command;
}
});
}
}
if (gURLBar) {

View File

@ -16,9 +16,50 @@ function test() {
whenDelayedStartupFinished(privateWin, function() {
nonPrivateWin = privateWin.OpenBrowserWindow({private: false});
ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "privateWin.OpenBrowserWindow({private: false}) should open a normal window");
nonPrivateWin.close();
[
{ normal: "menu_newNavigator", private: "menu_newPrivateWindow", accesskey: true },
{ normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accesskey: false },
].forEach(function(menu) {
let newWindow = privateWin.document.getElementById(menu.normal);
let newPrivateWindow = privateWin.document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
ok(!newPrivateWindow.hidden, "New Private Window menu item should be hidden");
isnot(newWindow.label, newPrivateWindow.label, "New Window's label shouldn't be overwritten");
if (menu.accesskey) {
isnot(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey shouldn't be overwritten");
}
isnot(newWindow.command, newPrivateWindow.command, "New Window's command shouldn't be overwritten");
}
});
privateWin.close();
finish();
Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
privateWin = OpenBrowserWindow({private: true});
whenDelayedStartupFinished(privateWin, function() {
[
{ normal: "menu_newNavigator", private: "menu_newPrivateWindow", accessKey: true },
{ normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accessKey: false },
].forEach(function(menu) {
let newWindow = privateWin.document.getElementById(menu.normal);
let newPrivateWindow = privateWin.document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
ok(newPrivateWindow.hidden, "New Private Window menu item should be hidden");
is(newWindow.label, newPrivateWindow.label, "New Window's label should be overwritten");
if (menu.accesskey) {
is(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey should be overwritten");
}
is(newWindow.command, newPrivateWindow.command, "New Window's command should be overwritten");
}
});
privateWin.close();
Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
finish();
});
});
}

View File

@ -402,23 +402,32 @@ Toolbox.prototype = {
let id = toolDefinition.id;
let radio = this.doc.createElement("radio");
radio.setAttribute("label", toolDefinition.label);
radio.className = "toolbox-tab devtools-tab";
radio.id = "toolbox-tab-" + id;
radio.setAttribute("flex", "1");
radio.setAttribute("toolid", id);
radio.setAttribute("tooltiptext", toolDefinition.tooltip);
if (toolDefinition.icon) {
radio.setAttribute("src", toolDefinition.icon);
}
radio.addEventListener("command", function(id) {
this.selectTool(id);
}.bind(this, id));
if (toolDefinition.icon) {
let image = this.doc.createElement("image");
image.setAttribute("src", toolDefinition.icon);
radio.appendChild(image);
}
let label = this.doc.createElement("label");
label.setAttribute("value", toolDefinition.label)
label.setAttribute("crop", "end");
label.setAttribute("flex", "1");
let vbox = this.doc.createElement("vbox");
vbox.className = "toolbox-panel";
vbox.id = "toolbox-panel-" + id;
radio.appendChild(label);
tabs.appendChild(radio);
deck.appendChild(vbox);
@ -434,6 +443,13 @@ Toolbox.prototype = {
selectTool: function TBOX_selectTool(id) {
let deferred = Promise.defer();
let selected = this.doc.querySelector(".devtools-tab[selected]");
if (selected) {
selected.removeAttribute("selected");
}
let tab = this.doc.getElementById("toolbox-tab-" + id);
tab.setAttribute("selected", "true");
if (this._currentToolId == id) {
// Return the existing panel in order to have a consistent return value.
return Promise.resolve(this._toolPanels.get(id));

View File

@ -11,7 +11,8 @@ let toolbox, toolIDs, idIndex;
function test() {
waitForExplicitFinish();
if (window.navigator.oscpu.match(/osx 10\.8/i) || window.navigator.oscpu.match(/windows nt 5\.1/i)) {
if (window.navigator.userAgent.indexOf("Mac OS X 10.8") != -1 ||
window.navigator.userAgent.indexOf("Windows NT 5.1") != -1) {
info("Skipping Mac OSX 10.8 and Windows xp, see bug 838069");
finish();
return;

View File

@ -28,9 +28,9 @@
<hbox id="toolbox-dock-buttons"/>
</hbox>
#endif
<radiogroup id="toolbox-tabs" orient="horizontal">
</radiogroup>
<hbox id="toolbox-buttons" flex="1" pack="end"/>
<hbox id="toolbox-tabs" flex="1">
</hbox>
<hbox id="toolbox-buttons" pack="end"/>
#ifndef XP_MACOSX
<vbox id="toolbox-controls-separator"/>
<hbox id="toolbox-controls">

View File

@ -24,6 +24,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Highlighter",
"resource:///modules/devtools/Highlighter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ToolSidebar",
"resource:///modules/devtools/Sidebar.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SelectorSearch",
"resource:///modules/devtools/SelectorSearch.jsm");
const LAYOUT_CHANGE_TIMER = 250;
@ -50,9 +52,7 @@ InspectorPanel.prototype = {
open: function InspectorPanel_open() {
let deferred = Promise.defer();
this.preventNavigateAway = this.preventNavigateAway.bind(this);
this.onNavigatedAway = this.onNavigatedAway.bind(this);
this.target.on("will-navigate", this.preventNavigateAway);
this.target.on("navigate", this.onNavigatedAway);
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
@ -62,16 +62,6 @@ InspectorPanel.prototype = {
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
// Initialize the search related items
this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
this._lastSearched = null;
this._searchResults = null;
this._searchIndex = 0;
this._onHTMLSearch = this._onHTMLSearch.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this);
this.searchBox.addEventListener("command", this._onHTMLSearch, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
// Create an empty selection
this._selection = new Selection();
this.onNewSelection = this.onNewSelection.bind(this);
@ -153,6 +143,7 @@ InspectorPanel.prototype = {
deferred.resolve(this);
}.bind(this));
this.setupSearchBox();
this.setupSidebar();
return deferred.promise;
@ -195,6 +186,24 @@ InspectorPanel.prototype = {
this.isDirty = true;
},
/**
* Hooks the searchbar to show result and auto completion suggestions.
*/
setupSearchBox: function InspectorPanel_setupSearchBox() {
// Initiate the selectors search object.
let setNodeFunction = function(node) {
this.selection.setNode(node, "selectorsearch");
}.bind(this);
if (this.searchSuggestions) {
this.searchSuggestions.destroy();
this.searchSuggestions = null;
}
this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
this.searchSuggestions = new SelectorSearch(this.browser.contentDocument,
this.searchBox,
setNodeFunction);
},
/**
* Build the sidebar.
*/
@ -256,6 +265,7 @@ InspectorPanel.prototype = {
self.selection.setNode(newWindow.document.documentElement, "navigateaway");
}
self._initMarkup();
self.setupSearchBox();
}
if (newWindow.document.readyState == "loading") {
@ -265,77 +275,6 @@ InspectorPanel.prototype = {
}
},
/**
* Show a message if the inspector is dirty.
*/
preventNavigateAway: function InspectorPanel_preventNavigateAway(event, request) {
if (!this.isDirty) {
return;
}
request.suspend();
let notificationBox = null;
if (this.target.isLocalTab) {
let gBrowser = this.target.tab.ownerDocument.defaultView.gBrowser;
notificationBox = gBrowser.getNotificationBox();
}
else {
notificationBox = this._toolbox.getNotificationBox();
}
let notification = notificationBox.
getNotificationWithValue("inspector-page-navigation");
if (notification) {
notificationBox.removeNotification(notification, true);
}
let cancelRequest = function onCancelRequest() {
if (request) {
request.cancel(Cr.NS_BINDING_ABORTED);
request.resume(); // needed to allow the connection to be cancelled.
request = null;
}
};
let eventCallback = function onNotificationCallback(event) {
if (event == "removed") {
cancelRequest();
}
};
let buttons = [
{
id: "inspector.confirmNavigationAway.buttonLeave",
label: this.strings.GetStringFromName("confirmNavigationAway.buttonLeave"),
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
callback: function onButtonLeave() {
if (request) {
request.resume();
request = null;
}
}.bind(this),
},
{
id: "inspector.confirmNavigationAway.buttonStay",
label: this.strings.GetStringFromName("confirmNavigationAway.buttonStay"),
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
callback: cancelRequest
},
];
let message = this.strings.GetStringFromName("confirmNavigationAway.message2");
notification = notificationBox.appendNotification(message,
"inspector-page-navigation", "chrome://browser/skin/Info.png",
notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
// Make sure this not a transient notification, to avoid the automatic
// transient notification removal.
notification.persistence = -1;
},
/**
* When a new node is selected.
*/
@ -379,7 +318,6 @@ InspectorPanel.prototype = {
this.browser = null;
}
this.target.off("will-navigate", this.preventNavigateAway);
this.target.off("navigate", this.onNavigatedAway);
if (this.highlighter) {
@ -400,9 +338,8 @@ InspectorPanel.prototype = {
this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.breadcrumbs.destroy();
this.searchSuggestions.destroy();
this.selection.off("new-node", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("detached", this.onDetached);
@ -414,82 +351,14 @@ InspectorPanel.prototype = {
this.panelDoc = null;
this.panelWin = null;
this.breadcrumbs = null;
this.searchSuggestions = null;
this.lastNodemenuItem = null;
this.nodemenu = null;
this.searchBox = null;
this.highlighter = null;
this._searchResults = null;
return Promise.resolve(null);
},
/**
* The command callback for the HTML search box. This function is
* automatically invoked as the user is typing.
*/
_onHTMLSearch: function InspectorPanel__onHTMLSearch() {
let query = this.searchBox.value;
if (query == this._lastSearched) {
return;
}
this._lastSearched = query;
this._searchIndex = 0;
if (query.length == 0) {
this.searchBox.removeAttribute("filled");
this.searchBox.classList.remove("devtools-no-search-result");
return;
}
this.searchBox.setAttribute("filled", true);
this._searchResults = this.browser.contentDocument.querySelectorAll(query);
if (this._searchResults.length > 0) {
this.searchBox.classList.remove("devtools-no-search-result");
this.cancelLayoutChange();
this.selection.setNode(this._searchResults[0]);
} else {
this.searchBox.classList.add("devtools-no-search-result");
}
},
/**
* Search for the search box value as a query selector.
*/
_onSearchKeypress: function InspectorPanel__onSearchKeypress(aEvent) {
let query = this.searchBox.value;
switch(aEvent.keyCode) {
case aEvent.DOM_VK_ENTER:
case aEvent.DOM_VK_RETURN:
if (query == this._lastSearched) {
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
} else {
this._onHTMLSearch();
return;
}
break;
case aEvent.DOM_VK_UP:
if (--this._searchIndex < 0) {
this._searchIndex = this._searchResults.length - 1;
}
break;
case aEvent.DOM_VK_DOWN:
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
break;
default:
return;
}
aEvent.preventDefault();
aEvent.stopPropagation();
this.cancelLayoutChange();
if (this._searchResults.length > 0) {
this.selection.setNode(this._searchResults[this._searchIndex]);
}
},
/**
* Show the node menu.
*/

View File

@ -0,0 +1,549 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
"resource:///modules/devtools/AutocompletePopup.jsm");
this.EXPORTED_SYMBOLS = ["SelectorSearch"];
// Maximum number of selector suggestions shown in the panel.
const MAX_SUGGESTIONS = 15;
/**
* Converts any input box on a page to a CSS selector search and suggestion box.
*
* @constructor
* @param nsIDOMDocument aContentDocument
* The content document which inspector is attached to.
* @param nsiInputElement aInputNode
* The input element to which the panel will be attached and from where
* search input will be taken.
* @param Function aCallback
* The method to callback when a search is available.
* This method is called with the matched node as the first argument.
*/
this.SelectorSearch = function(aContentDocument, aInputNode, aCallback) {
this.doc = aContentDocument;
this.callback = aCallback;
this.searchBox = aInputNode;
this.panelDoc = this.searchBox.ownerDocument;
// initialize variables.
this._lastSearched = null;
this._lastValidSearch = "";
this._lastToLastValidSearch = null;
this._searchResults = null;
this._searchSuggestions = {};
this._searchIndex = 0;
// bind!
this._showPopup = this._showPopup.bind(this);
this._onHTMLSearch = this._onHTMLSearch.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this);
this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
// Options for the AutocompletePopup.
let options = {
panelId: "inspector-searchbox-panel",
listBoxId: "searchbox-panel-listbox",
fixedWidth: true,
autoSelect: true,
position: "before_start",
direction: "ltr",
onClick: this._onListBoxKeypress,
onKeypress: this._onListBoxKeypress,
};
this.searchPopup = new AutocompletePopup(this.panelDoc, options);
// event listeners.
this.searchBox.addEventListener("command", this._onHTMLSearch, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
}
this.SelectorSearch.prototype = {
// The possible states of the query.
States: {
CLASS: "class",
ID: "id",
TAG: "tag",
},
// The current state of the query.
_state: null,
// The query corresponding to last state computation.
_lastStateCheckAt: null,
/**
* Computes the state of the query. State refers to whether the query
* currently requires a class suggestion, or a tag, or an Id suggestion.
* This getter will effectively compute the state by traversing the query
* character by character each time the query changes.
*
* @example
* '#f' requires an Id suggestion, so the state is States.ID
* 'div > .foo' requires class suggestion, so state is States.CLASS
*/
get state() {
if (!this.searchBox || !this.searchBox.value) {
return null;
}
let query = this.searchBox.value;
if (this._lastStateCheckAt == query) {
// If query is the same, return early.
return this._state;
}
this._lastStateCheckAt = query;
this._state = null;
let subQuery = "";
// Now we iterate over the query and decide the state character by character.
// The logic here is that while iterating, the state can go from one to
// another with some restrictions. Like, if the state is Class, then it can
// never go to Tag state without a space or '>' character; Or like, a Class
// state with only '.' cannot go to an Id state without any [a-zA-Z] after
// the '.' which means that '.#' is a selector matching a class name '#'.
// Similarily for '#.' which means a selctor matching an id '.'.
for (let i = 1; i <= query.length; i++) {
// Calculate the state.
subQuery = query.slice(0, i);
let [secondLastChar, lastChar] = subQuery.slice(-2);
switch (this._state) {
case null:
// This will happen only in the first iteration of the for loop.
lastChar = secondLastChar;
case this.States.TAG:
this._state = lastChar == "."
? this.States.CLASS
: lastChar == "#"
? this.States.ID
: this.States.TAG;
break;
case this.States.CLASS:
if (subQuery.match(/[\.]+[^\.]*$/)[0].length > 2) {
// Checks whether the subQuery has atleast one [a-zA-Z] after the '.'.
this._state = (lastChar == " " || lastChar == ">")
? this.States.TAG
: lastChar == "#"
? this.States.ID
: this.States.CLASS;
}
break;
case this.States.ID:
if (subQuery.match(/[#]+[^#]*$/)[0].length > 2) {
// Checks whether the subQuery has atleast one [a-zA-Z] after the '#'.
this._state = (lastChar == " " || lastChar == ">")
? this.States.TAG
: lastChar == "."
? this.States.CLASS
: this.States.ID;
}
break;
}
}
return this._state;
},
/**
* Removes event listeners and cleans up references.
*/
destroy: function SelectorSearch_destroy() {
// event listeners.
this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.searchPopup.destroy();
this.searchPopup = null;
this.searchBox = null;
this.doc = null;
this.panelDoc = null;
this._searchResults = null;
this._searchSuggestions = null;
this.callback = null;
},
/**
* The command callback for the input box. This function is automatically
* invoked as the user is typing if the input box type is search.
*/
_onHTMLSearch: function SelectorSearch__onHTMLSearch() {
let query = this.searchBox.value;
if (query == this._lastSearched) {
return;
}
this._lastSearched = query;
this._searchIndex = 0;
if (query.length == 0) {
this._lastValidSearch = "";
this.searchBox.removeAttribute("filled");
this.searchBox.classList.remove("devtools-no-search-result");
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
return;
}
this.searchBox.setAttribute("filled", true);
try {
this._searchResults = this.doc.querySelectorAll(query);
}
catch (ex) {
this._searchResults = [];
}
if (this._searchResults.length > 0) {
this._lastValidSearch = query;
// Even though the selector matched atleast one node, there is still
// possibility of suggestions.
if (query.match(/[\s>+]$/)) {
// If the query has a space or '>' at the end, create a selector to match
// the children of the selector inside the search box by adding a '*'.
this._lastValidSearch += "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
// If the query is a partial descendant selector which does not matches
// any node, remove the last incomplete part and add a '*' to match
// everything. For ex, convert 'foo > b' to 'foo > *' .
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
if (!query.slice(-1).match(/[\.#\s>+]/)) {
// Hide the popup if we have some matching nodes and the query is not
// ending with [.# >] which means that the selector is not at the
// beginning of a new class, tag or id.
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
}
else {
this.showSuggestions();
}
this.searchBox.classList.remove("devtools-no-search-result");
this.callback(this._searchResults[0]);
}
else {
if (query.match(/[\s>+]$/)) {
this._lastValidSearch = query + "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
this.searchBox.classList.add("devtools-no-search-result");
this.showSuggestions();
}
},
/**
* Handles keypresses inside the input box.
*/
_onSearchKeypress: function SelectorSearch__onSearchKeypress(aEvent) {
let query = this.searchBox.value;
switch(aEvent.keyCode) {
case aEvent.DOM_VK_ENTER:
case aEvent.DOM_VK_RETURN:
if (query == this._lastSearched) {
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
}
else {
this._onHTMLSearch();
return;
}
break;
case aEvent.DOM_VK_UP:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex =
Math.max(0, this.searchPopup.itemCount - 2);
}
else {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
}
this.searchBox.value = this.searchPopup.selectedItem.label;
}
else if (--this._searchIndex < 0) {
this._searchIndex = this._searchResults.length - 1;
}
break;
case aEvent.DOM_VK_DOWN:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
this.searchPopup.selectedIndex = 0;
this.searchBox.value = this.searchPopup.selectedItem.label;
}
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
break;
case aEvent.DOM_VK_TAB:
if (this.searchPopup.isOpen &&
this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
.preLabel == query) {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
this.searchBox.value = this.searchPopup.selectedItem.label;
this._onHTMLSearch();
}
break;
case aEvent.DOM_VK_BACK_SPACE:
case aEvent.DOM_VK_DELETE:
// need to throw away the lastValidSearch.
this._lastToLastValidSearch = null;
// This gets the most complete selector from the query. For ex.
// '.foo.ba' returns '.foo' , '#foo > .bar.baz' returns '#foo > .bar'
// '.foo +bar' returns '.foo +' and likewise.
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
return;
default:
return;
}
aEvent.preventDefault();
aEvent.stopPropagation();
if (this._searchResults.length > 0) {
this.callback(this._searchResults[this._searchIndex]);
}
},
/**
* Handles keypress and mouse click on the suggestions richlistbox.
*/
_onListBoxKeypress: function SelectorSearch__onListBoxKeypress(aEvent) {
switch(aEvent.keyCode || aEvent.button) {
case aEvent.DOM_VK_ENTER:
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_TAB:
case 0: // left mouse button
aEvent.stopPropagation();
aEvent.preventDefault();
this.searchBox.value = this.searchPopup.selectedItem.label;
this.searchBox.focus();
this._onHTMLSearch();
break;
case aEvent.DOM_VK_UP:
if (this.searchPopup.selectedIndex == 0) {
this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation();
aEvent.preventDefault();
this.searchBox.focus();
}
else {
let index = this.searchPopup.selectedIndex;
this.searchBox.value = this.searchPopup.getItemAtIndex(index - 1).label;
}
break;
case aEvent.DOM_VK_DOWN:
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation();
aEvent.preventDefault();
this.searchBox.focus();
}
else {
let index = this.searchPopup.selectedIndex;
this.searchBox.value = this.searchPopup.getItemAtIndex(index + 1).label;
}
break;
case aEvent.DOM_VK_BACK_SPACE:
aEvent.stopPropagation();
aEvent.preventDefault();
this.searchBox.focus();
if (this.searchBox.selectionStart > 0) {
this.searchBox.value =
this.searchBox.value.substring(0, this.searchBox.selectionStart - 1);
}
this._lastToLastValidSearch = null;
let query = this.searchBox.value;
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
this._onHTMLSearch();
break;
}
},
/**
* Populates the suggestions list and show the suggestion popup.
*/
_showPopup: function SelectorSearch__showPopup(aList, aFirstPart) {
// Sort alphabetically in increaseing order.
aList = aList.sort();
// Sort based on count= in decreasing order.
aList = aList.sort(function([a1,a2], [b1,b2]) {
return a2 < b2;
});
let total = 0;
let query = this.searchBox.value;
let toLowerCase = false;
let items = [];
// In case of tagNames, change the case to small.
if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
toLowerCase = true;
}
for (let [value, count] of aList) {
// for cases like 'div ' or 'div >' or 'div+'
if (query.match(/[\s>+]$/)) {
value = query + value;
}
// for cases like 'div #a' or 'div .a' or 'div > d' and likewise
else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
}
// for cases like 'div.class' or '#foo.bar' and likewise
else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
}
let item = {
preLabel: query,
label: value,
count: count
};
if (toLowerCase) {
item.label = value.toLowerCase();
}
items.unshift(item);
if (++total > MAX_SUGGESTIONS - 1) {
break;
}
}
if (total > 0) {
this.searchPopup.setItems(items);
this.searchPopup.openPopup(this.searchBox);
}
else {
this.searchPopup.hidePopup();
}
},
/**
* Suggests classes,ids and tags based on the user input as user types in the
* searchbox.
*/
showSuggestions: function SelectorSearch_showSuggestions() {
let query = this.searchBox.value;
if (this._lastValidSearch != "" &&
this._lastToLastValidSearch != this._lastValidSearch) {
this._searchSuggestions = {
ids: new Map(),
classes: new Map(),
tags: new Map(),
};
let nodes = [];
try {
nodes = this.doc.querySelectorAll(this._lastValidSearch);
} catch (ex) {}
for (let node of nodes) {
this._searchSuggestions.ids.set(node.id, 1);
this._searchSuggestions.tags
.set(node.tagName,
(this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
for (let className of node.classList) {
this._searchSuggestions.classes
.set(className,
(this._searchSuggestions.classes.get(className) || 0) + 1);
}
}
this._lastToLastValidSearch = this._lastValidSearch;
}
else if (this._lastToLastValidSearch != this._lastValidSearch) {
this._searchSuggestions = {
ids: new Map(),
classes: new Map(),
tags: new Map(),
};
if (query.length == 0) {
return;
}
let nodes = null;
if (this.state == this.States.CLASS) {
nodes = this.doc.querySelectorAll("[class]");
for (let node of nodes) {
for (let className of node.classList) {
this._searchSuggestions.classes
.set(className,
(this._searchSuggestions.classes.get(className) || 0) + 1);
}
}
}
else if (this.state == this.States.ID) {
nodes = this.doc.querySelectorAll("[id]");
for (let node of nodes) {
this._searchSuggestions.ids.set(node.id, 1);
}
}
else if (this.state == this.States.TAG) {
nodes = this.doc.getElementsByTagName("*");
for (let node of nodes) {
this._searchSuggestions.tags
.set(node.tagName,
(this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
}
}
else {
return;
}
this._lastToLastValidSearch = this._lastValidSearch;
}
// Filter the suggestions based on search box value.
let result = [];
let firstPart = "";
if (this.state == this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["",query])[1];
for (let [tag, count] of this._searchSuggestions.tags) {
if (tag.toLowerCase().startsWith(firstPart.toLowerCase())) {
result.push([tag, count]);
}
}
}
else if (this.state == this.States.CLASS) {
// gets the class that is being completed. For ex. '.foo.b' returns 'b'
firstPart = query.match(/\.([^\.]*)$/)[1];
for (let [className, count] of this._searchSuggestions.classes) {
if (className.startsWith(firstPart)) {
result.push(["." + className, count]);
}
}
firstPart = "." + firstPart;
}
else if (this.state == this.States.ID) {
// gets the id that is being completed. For ex. '.foo#b' returns 'b'
firstPart = query.match(/#([^#]*)$/)[1];
for (let [id, count] of this._searchSuggestions.ids) {
if (id.startsWith(firstPart)) {
result.push(["#" + id, 1]);
}
}
firstPart = "#" + firstPart;
}
this._showPopup(result, firstPart);
},
};

View File

@ -6,3 +6,23 @@
#inspector-sidebar {
min-width: 250px;
}
#searchbox-panel-listbox {
width: 250px;
max-width: 250px;
overflow-x: hidden;
}
#searchbox-panel-listbox > richlistitem,
#searchbox-panel-listbox > richlistitem[selected] {
overflow-x: hidden;
}
#searchbox-panel-listbox > richlistitem > .initial-value {
max-width: 130px;
margin-left: 15px;
}
#searchbox-panel-listbox > richlistitem > .autocomplete-value {
max-width: 150px;
}

View File

@ -28,7 +28,6 @@ _BROWSER_FILES = \
browser_inspector_destroyselection.js \
browser_inspector_bug_699308_iframe_navigation.js \
browser_inspector_bug_672902_keyboard_shortcuts.js \
browser_inspector_bug_566084_location_changed.js \
browser_inspector_sidebarstate.js \
browser_inspector_pseudoclass_lock.js \
browser_inspector_cmd_inspect.js \
@ -39,6 +38,10 @@ _BROWSER_FILES = \
browser_inspector_bug_817558_delete_node.js \
browser_inspector_bug_650804_search.js \
browser_inspector_bug_650804_search.html \
browser_inspector_bug_831693_input_suggestion.js \
browser_inspector_bug_831693_searchbox_panel_navigation.js \
browser_inspector_bug_831693_combinator_suggestions.js \
browser_inspector_bug_831693_search_suggestions.html \
browser_inspector_bug_835722_infobar_reappears.js \
browser_inspector_bug_840156_destroy_after_navigation.js \
head.js \

View File

@ -1,131 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let tempScope = {};
Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
let TargetFactory = tempScope.TargetFactory;
function test() {
let notificationBox, inspector;
let alertActive1_called = false;
let alertActive2_called = false;
function startLocationTests() {
openInspector(runInspectorTests);
}
function runInspectorTests(aInspector) {
inspector = aInspector;
let para = content.document.querySelector("p");
ok(para, "found the paragraph element");
is(para.textContent, "init", "paragraph content is correct");
inspector.markDirty();
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
notificationBox = gBrowser.getNotificationBox();
notificationBox.addEventListener("AlertActive", alertActive1, false);
ok(toolbox, "We have access to the notificationBox");
gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
content.location = "data:text/html,<div>location change test 1 for " +
"inspector</div><p>test1</p>";
}
function alertActive1() {
alertActive1_called = true;
notificationBox.removeEventListener("AlertActive", alertActive1, false);
let notification = notificationBox.
getNotificationWithValue("inspector-page-navigation");
ok(notification, "found the inspector-page-navigation notification");
// By closing the notification it is expected that page navigation is
// canceled.
executeSoon(function() {
notification.close();
locationTest2();
});
}
function locationTest2() {
// Location did not change.
let para = content.document.querySelector("p");
ok(para, "found the paragraph element, second time");
is(para.textContent, "init", "paragraph content is correct");
let target = TargetFactory.forTab(gBrowser.selectedTab);
let inspector = gDevTools.getToolbox(target).getPanel("inspector");
ok(inspector, "Inspector still alive");
notificationBox.addEventListener("AlertActive", alertActive2, false);
content.location = "data:text/html,<div>location change test 2 for " +
"inspector</div><p>test2</p>";
}
function alertActive2() {
alertActive2_called = true;
notificationBox.removeEventListener("AlertActive", alertActive2, false);
let notification = notificationBox.
getNotificationWithValue("inspector-page-navigation");
ok(notification, "found the inspector-page-navigation notification");
let buttons = notification.querySelectorAll("button");
let buttonLeave = null;
for (let i = 0; i < buttons.length; i++) {
if (buttons[i].buttonInfo.id == "inspector.confirmNavigationAway.buttonLeave") {
buttonLeave = buttons[i];
break;
}
}
ok(buttonLeave, "the Leave page button was found");
// Accept page navigation.
executeSoon(function(){
buttonLeave.doCommand();
});
}
function onPageLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true);
isnot(content.location.href.indexOf("test2"), -1,
"page navigated to the correct location");
let para = content.document.querySelector("p");
ok(para, "found the paragraph element, third time");
is(para.textContent, "test2", "paragraph content is correct");
let root = content.document.documentElement;
is(inspector.selection.node, root, "Selection is the root of the new page.");
ok(alertActive1_called, "first notification box has been showed");
ok(alertActive2_called, "second notification box has been showed");
testEnd();
}
function testEnd() {
notificationBox = null;
gBrowser.removeCurrentTab();
executeSoon(finish);
}
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
waitForFocus(startLocationTests, content);
}, true);
content.location = "data:text/html,<div>location change tests for " +
"inspector.</div><p>init</p>";
}

View File

@ -20,7 +20,7 @@ function test()
["v", "d1", true],
["VK_DOWN", "d2", true],
["VK_ENTER", "d1", true],
[".", "d1", true],
[".", "d1", false],
["c", "d1", false],
["1", "d2", true],
["VK_DOWN", "d2", true],
@ -30,7 +30,7 @@ function test()
["VK_BACK_SPACE", "d1", false],
["VK_BACK_SPACE", "d1", false],
["VK_BACK_SPACE", "d1", true],
[".", "d1", true],
[".", "d1", false],
["c", "d1", false],
["1", "d2", true],
["VK_DOWN", "s2", true],

View File

@ -0,0 +1,113 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
waitForExplicitFinish();
let inspector, searchBox, state, popup;
// The various states of the inspector: [key, suggestions array]
// [
// what key to press,
// suggestions array with count [
// [suggestion1, count1], [suggestion2] ...
// ] count can be left to represent 1
// ]
let keyStates = [
["d", [["div", 4]]],
["i", [["div", 4]]],
["v", []],
[" ", [["div div", 2], ["div span", 2]]],
[">", [["div >div", 2], ["div >span", 2]]],
["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
["+", [["div +span"]]],
["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
["VK_BACK_SPACE", []],
["VK_BACK_SPACE", [["div", 4]]],
["VK_BACK_SPACE", [["div", 4]]],
["VK_BACK_SPACE", []],
["p", []],
[" ", [["p strong"]]],
["+", [["p +button"], ["p +p"]]],
["b", [["p +button"]]],
["u", [["p +button"]]],
["t", [["p +button"]]],
["t", [["p +button"]]],
["o", [["p +button"]]],
["n", []],
["+", [["p +button+p"]]],
];
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_bug_831693_search_suggestions.html";
function $(id) {
if (id == null) return null;
return content.document.getElementById(id);
}
function setupTest()
{
openInspector(startTest);
}
function startTest(aInspector)
{
inspector = aInspector;
searchBox =
inspector.panelWin.document.getElementById("inspector-searchbox");
popup = inspector.searchSuggestions.searchPopup;
focusSearchBoxUsingShortcut(inspector.panelWin, function() {
searchBox.addEventListener("command", checkState, true);
checkStateAndMoveOn(0);
});
}
function checkStateAndMoveOn(index) {
if (index == keyStates.length) {
finishUp();
return;
}
let [key, suggestions] = keyStates[index];
state = index;
info("pressing key " + key + " to get suggestions " +
JSON.stringify(suggestions));
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
}
function checkState(event) {
executeSoon(function() {
let [key, suggestions] = keyStates[state];
let actualSuggestions = popup.getItems();
is(popup._panel.state == "open" || popup._panel.state == "showing"
? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions at " + state + "th step.");
actualSuggestions = actualSuggestions.reverse();
for (let i = 0; i < suggestions.length; i++) {
is(suggestions[i][0], actualSuggestions[i].label,
"The suggestion at " + i + "th index for " + state +
"th step is correct.")
is(suggestions[i][1] || 1, actualSuggestions[i].count,
"The count for suggestion at " + i + "th index for " + state +
"th step is correct.")
}
checkStateAndMoveOn(state + 1);
});
}
function finishUp() {
searchBox = null;
popup = null;
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -0,0 +1,115 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
waitForExplicitFinish();
let inspector, searchBox, state, popup;
// The various states of the inspector: [key, suggestions array]
// [
// what key to press,
// suggestions array with count [
// [suggestion1, count1], [suggestion2] ...
// ] count can be left to represent 1
// ]
let keyStates = [
["d", [["div", 2]]],
["i", [["div", 2]]],
["v", []],
[".", [["div.c1"]]],
["VK_BACK_SPACE", []],
["#", [["div#d1"], ["div#d2"]]],
["VK_BACK_SPACE", []],
["VK_BACK_SPACE", [["div", 2]]],
["VK_BACK_SPACE", [["div", 2]]],
["VK_BACK_SPACE", []],
[".", [[".c1", 3], [".c2"]]],
["c", [[".c1", 3], [".c2"]]],
["2", []],
["VK_BACK_SPACE", [[".c1", 3], [".c2"]]],
["1", []],
["#", [["#d2"], ["#p1"], ["#s2"]]],
["VK_BACK_SPACE", []],
["VK_BACK_SPACE", [[".c1", 3], [".c2"]]],
["VK_BACK_SPACE", [[".c1", 3], [".c2"]]],
["VK_BACK_SPACE", []],
["#", [["#b1"], ["#d1"], ["#d2"], ["#p1"], ["#p2"], ["#p3"], ["#s1"], ["#s2"]]],
["p", [["#p1"], ["#p2"], ["#p3"]]],
["VK_BACK_SPACE", [["#b1"], ["#d1"], ["#d2"], ["#p1"], ["#p2"], ["#p3"], ["#s1"], ["#s2"]]],
["VK_BACK_SPACE", []],
];
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_bug_650804_search.html";
function $(id) {
if (id == null) return null;
return content.document.getElementById(id);
}
function setupTest()
{
openInspector(startTest);
}
function startTest(aInspector)
{
inspector = aInspector;
searchBox =
inspector.panelWin.document.getElementById("inspector-searchbox");
popup = inspector.searchSuggestions.searchPopup;
focusSearchBoxUsingShortcut(inspector.panelWin, function() {
searchBox.addEventListener("command", checkState, true);
checkStateAndMoveOn(0);
});
}
function checkStateAndMoveOn(index) {
if (index == keyStates.length) {
finishUp();
return;
}
let [key, suggestions] = keyStates[index];
state = index;
info("pressing key " + key + " to get suggestions " +
JSON.stringify(suggestions));
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
}
function checkState(event) {
executeSoon(function() {
let [key, suggestions] = keyStates[state];
let actualSuggestions = popup.getItems();
is(popup._panel.state == "open" || popup._panel.state == "showing"
? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions at " + state + "th step.");
actualSuggestions = actualSuggestions.reverse();
for (let i = 0; i < suggestions.length; i++) {
is(suggestions[i][0], actualSuggestions[i].label,
"The suggestion at " + i + "th index for " + state +
"th step is correct.")
is(suggestions[i][1] || 1, actualSuggestions[i].count,
"The count for suggestion at " + i + "th index for " + state +
"th step is correct.")
}
checkStateAndMoveOn(state + 1);
});
}
function finishUp() {
searchBox = null;
popup = null;
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Inspector Search Box Test</title>
</head>
<body>
<div id="d1">
<div class="l1">
<div id="d2" class="c1">Hello, I'm nested div</div>
</div>
</div>
<span id="s1">Hello, I'm a span
<div class="l1">
<span>Hi I am a nested span</span>
<span class="s4">Hi I am a nested classed span</span>
</div>
</span>
<span class="c1" id="s2">And me</span>
<p class="c1" id="p1">.someclass</p>
<p id="p2">#someid</p>
<button id="b1" disabled>button[disabled]</button>
<p id="p3" class="c2"><strong>p&gt;strong</strong></p>
</body>
</html>

View File

@ -0,0 +1,156 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
waitForExplicitFinish();
requestLongerTimeout(2);
let inspector, searchBox, state, panel;
let panelOpeningStates = [0, 3, 14, 17];
let panelClosingStates = [2, 13, 16];
// The various states of the inspector: [key, query]
// [
// what key to press,
// what should be the text in the searchbox
// ]
let keyStates = [
["d", "d"],
["i", "di"],
["v", "div"],
[".", "div."],
["VK_UP", "div.c1"],
["VK_DOWN", "div.l1"],
["VK_DOWN", "div.l1"],
["VK_BACK_SPACE", "div.l"],
["VK_TAB", "div.l1"],
[" ", "div.l1 "],
["VK_UP", "div.l1 DIV"],
["VK_UP", "div.l1 DIV"],
[".", "div.l1 DIV."],
["VK_TAB", "div.l1 DIV.c1"],
["VK_BACK_SPACE", "div.l1 DIV.c"],
["VK_BACK_SPACE", "div.l1 DIV."],
["VK_BACK_SPACE", "div.l1 DIV"],
["VK_BACK_SPACE", "div.l1 DI"],
["VK_BACK_SPACE", "div.l1 D"],
["VK_BACK_SPACE", "div.l1 "],
["VK_UP", "div.l1 DIV"],
["VK_BACK_SPACE", "div.l1 DI"],
["VK_BACK_SPACE", "div.l1 D"],
["VK_BACK_SPACE", "div.l1 "],
["VK_UP", "div.l1 DIV"],
["VK_UP", "div.l1 DIV"],
["VK_TAB", "div.l1 DIV"],
["VK_BACK_SPACE", "div.l1 DI"],
["VK_BACK_SPACE", "div.l1 D"],
["VK_BACK_SPACE", "div.l1 "],
["VK_DOWN", "div.l1 DIV"],
["VK_DOWN", "div.l1 SPAN"],
["VK_DOWN", "div.l1 SPAN"],
["VK_BACK_SPACE", "div.l1 SPA"],
["VK_BACK_SPACE", "div.l1 SP"],
["VK_BACK_SPACE", "div.l1 S"],
["VK_BACK_SPACE", "div.l1 "],
["VK_BACK_SPACE", "div.l1"],
["VK_BACK_SPACE", "div.l"],
["VK_BACK_SPACE", "div."],
["VK_BACK_SPACE", "div"],
["VK_BACK_SPACE", "di"],
["VK_BACK_SPACE", "d"],
["VK_BACK_SPACE", ""],
];
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_bug_831693_search_suggestions.html";
function $(id) {
if (id == null) return null;
return content.document.getElementById(id);
}
function setupTest()
{
openInspector(startTest);
}
function startTest(aInspector)
{
inspector = aInspector;
searchBox =
inspector.panelWin.document.getElementById("inspector-searchbox");
panel = inspector.searchSuggestions.searchPopup._list;
focusSearchBoxUsingShortcut(inspector.panelWin, function() {
searchBox.addEventListener("keypress", checkState, true);
panel.addEventListener("keypress", checkState, true);
checkStateAndMoveOn(0);
});
}
function checkStateAndMoveOn(index) {
if (index == keyStates.length) {
finishUp();
return;
}
let [key, query] = keyStates[index];
state = index;
info("pressing key " + key + " to get searchbox value as " + query);
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
}
function checkState(event) {
if (panelOpeningStates.indexOf(state) != -1 &&
!inspector.searchSuggestions.searchPopup.isOpen) {
info("Panel is not open, should wait before it shows up.");
panel.parentNode.addEventListener("popupshown", function retry() {
panel.parentNode.removeEventListener("popupshown", retry, false);
info("Panel is visible now");
executeSoon(checkState);
}, false);
return;
}
else if (panelClosingStates.indexOf(state) != -1 &&
panel.parentNode.state != "closed") {
info("Panel is open, should wait for it to close.");
panel.parentNode.addEventListener("popuphidden", function retry() {
panel.parentNode.removeEventListener("popuphidden", retry, false);
info("Panel is hidden now");
executeSoon(checkState);
}, false);
return;
}
// Using setTimout as the "command" event fires at delay after keypress
window.setTimeout(function() {
let [key, query] = keyStates[state];
if (searchBox.value == query) {
ok(true, "The suggestion at " + state + "th step on " +
"pressing " + key + " key is correct.");
}
else {
info("value is not correct, waiting longer for state " + state +
" with panel " + panel.parentNode.state);
checkState();
return;
}
checkStateAndMoveOn(state + 1);
}, 200);
}
function finishUp() {
searchBox = null;
panel = null;
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -7,19 +7,10 @@ const Cu = Components.utils;
// The XUL and XHTML namespace.
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const HUD_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
return Services.strings.createBundle(HUD_STRINGS_URI);
});
this.EXPORTED_SYMBOLS = ["AutocompletePopup"];
/**
@ -28,21 +19,48 @@ this.EXPORTED_SYMBOLS = ["AutocompletePopup"];
* @constructor
* @param nsIDOMDocument aDocument
* The document you want the popup attached to.
* @param Object aOptions
* An object consiting any of the following options:
* - panelId {String} The id for the popup panel.
* - listBoxId {String} The id for the richlistbox inside the panel.
* - position {String} The position for the popup panel.
* - theme {String} String related to the theme of the popup.
* - autoSelect {Boolean} Boolean to allow the first entry of the popup
* panel to be automatically selected when the popup shows.
* - fixedWidth {Boolean} Boolean to control dynamic width of the popup.
* - direction {String} The direction of the text in the panel. rtl or ltr
* - onSelect {String} The select event handler for the richlistbox
* - onClick {String} The click event handler for the richlistbox.
* - onKeypress {String} The keypress event handler for the richlistitems.
*/
this.AutocompletePopup = function AutocompletePopup(aDocument)
this.AutocompletePopup =
function AutocompletePopup(aDocument, aOptions = {})
{
this._document = aDocument;
this.fixedWidth = aOptions.fixedWidth || false;
this.autoSelect = aOptions.autoSelect || false;
this.position = aOptions.position || "after_start";
this.direction = aOptions.direction || "ltr";
this.onSelect = aOptions.onSelect;
this.onClick = aOptions.onClick;
this.onKeypress = aOptions.onKeypress;
let id = aOptions.panelId || "devtools_autoCompletePopup";
let theme = aOptions.theme || "dark";
// Reuse the existing popup elements.
this._panel = this._document.getElementById("webConsole_autocompletePopup");
this._panel = this._document.getElementById(id);
if (!this._panel) {
this._panel = this._document.createElementNS(XUL_NS, "panel");
this._panel.setAttribute("id", "webConsole_autocompletePopup");
this._panel.setAttribute("label",
stringBundle.GetStringFromName("Autocomplete.label"));
this._panel.setAttribute("id", id);
this._panel.className = "devtools-autocomplete-popup " + theme + "-theme";
this._panel.setAttribute("noautofocus", "true");
this._panel.setAttribute("ignorekeys", "true");
this._panel.setAttribute("level", "top");
if (!aOptions.onKeypress) {
this._panel.setAttribute("ignorekeys", "true");
}
let mainPopupSet = this._document.getElementById("mainPopupSet");
if (mainPopupSet) {
@ -51,22 +69,40 @@ this.AutocompletePopup = function AutocompletePopup(aDocument)
else {
this._document.documentElement.appendChild(this._panel);
}
this._list = this._document.createElementNS(XUL_NS, "richlistbox");
this._list.flex = 1;
this._panel.appendChild(this._list);
// Open and hide the panel, so we initialize the API of the richlistbox.
this._panel.width = 1;
this._panel.height = 1;
this._panel.openPopup(null, "overlap", 0, 0, false, false);
this._panel.hidePopup();
this._panel.width = "";
this._panel.height = "";
this._list = null;
}
else {
this._list = this._panel.firstChild;
}
if (!this._list) {
this._list = this._document.createElementNS(XUL_NS, "richlistbox");
this._panel.appendChild(this._list);
// Open and hide the panel, so we initialize the API of the richlistbox.
this._panel.openPopup(null, this.popup, 0, 0);
this._panel.hidePopup();
}
this._list.flex = 1;
this._list.setAttribute("seltype", "single");
if (aOptions.listBoxId) {
this._list.setAttribute("id", aOptions.listBoxId);
}
this._list.className = "devtools-autocomplete-listbox " + theme + "-theme";
if (this.onSelect) {
this._list.addEventListener("select", this.onSelect, false);
}
if (this.onClick) {
this._list.addEventListener("click", this.onClick, false);
}
if (this.onKeypress) {
this._list.addEventListener("keypress", this.onKeypress, false);
}
}
AutocompletePopup.prototype = {
@ -74,6 +110,11 @@ AutocompletePopup.prototype = {
_panel: null,
_list: null,
// Event handlers.
onSelect: null,
onClick: null,
onKeypress: null,
/**
* Open the autocomplete popup panel.
*
@ -82,17 +123,14 @@ AutocompletePopup.prototype = {
*/
openPopup: function AP_openPopup(aAnchor)
{
this._panel.openPopup(aAnchor, "after_start", 0, 0, false, false);
this._panel.openPopup(aAnchor, this.position, 0, 0);
if (this.onSelect) {
this._list.addEventListener("select", this.onSelect, false);
if (this.autoSelect) {
this.selectFirstItem();
}
if (this.onClick) {
this._list.addEventListener("click", this.onClick, false);
if (!this.fixedWidth) {
this._updateSize();
}
this._updateSize();
},
/**
@ -101,14 +139,6 @@ AutocompletePopup.prototype = {
hidePopup: function AP_hidePopup()
{
this._panel.hidePopup();
if (this.onSelect) {
this._list.removeEventListener("select", this.onSelect, false);
}
if (this.onClick) {
this._list.removeEventListener("click", this.onClick, false);
}
},
/**
@ -131,11 +161,35 @@ AutocompletePopup.prototype = {
}
this.clearItems();
if (this.onSelect) {
this._list.removeEventListener("select", this.onSelect, false);
}
if (this.onClick) {
this._list.removeEventListener("click", this.onClick, false);
}
if (this.onKeypress) {
this._list.removeEventListener("keypress", this.onKeypress, false);
}
this._document = null;
this._list = null;
this._panel = null;
},
/**
* Get the autocomplete items array.
*
* @param Number aIndex The index of the item what is wanted.
*
* @return The autocomplete item at index aIndex.
*/
getItemAtIndex: function AP_getItemAtIndex(aIndex)
{
return this._list.getItemAtIndex(aIndex)._autocompleteItem;
},
/**
* Get the autocomplete items array.
*
@ -166,9 +220,27 @@ AutocompletePopup.prototype = {
// Make sure that the new content is properly fitted by the XUL richlistbox.
if (this.isOpen) {
// We need the timeout to allow the content to reflow. Attempting to
// update the richlistbox size too early does not work.
this._document.defaultView.setTimeout(this._updateSize.bind(this), 1);
if (this.autoSelect) {
this.selectFirstItem();
}
if (!this.fixedWidth) {
this._updateSize();
}
}
},
/**
* Selects the first item of the richlistbox. Note that first item here is the
* item closes to the input element, which means that 0th index if position is
* below, and last index if position is above.
*/
selectFirstItem: function AP_selectFirstItem()
{
if (this.position.contains("before")) {
this.selectedIndex = this.itemCount - 1;
}
else {
this.selectedIndex = 0;
}
},
@ -179,11 +251,23 @@ AutocompletePopup.prototype = {
*/
_updateSize: function AP__updateSize()
{
if (!this._panel) {
return;
}
this._list.width = this._panel.clientWidth +
this._scrollbarWidth;
// We need the timeout to allow the content to reflow. Attempting to
// update the richlistbox size too early does not work.
this._document.defaultView.setTimeout(function() {
if (!this._panel) {
return;
}
this._list.width = this._panel.clientWidth +
this._scrollbarWidth;
// Height change is required, otherwise the panel is drawn at an offset
// the first time.
this._list.height = this._panel.clientHeight;
// This brings the panel back at right position.
this._list.top = 0;
// Changing panel height might make the selected item out of view, so
// bring it back to view.
this._list.ensureIndexIsVisible(this._list.selectedIndex);
}.bind(this), 5);
},
/**
@ -198,14 +282,16 @@ AutocompletePopup.prototype = {
this._list.removeChild(this._list.firstChild);
}
// Reset the panel and list dimensions. New dimensions are calculated when a
// new set of items is added to the autocomplete popup.
this._list.width = "";
this._list.height = "";
this._panel.width = "";
this._panel.height = "";
this._panel.top = "";
this._panel.left = "";
if (!this.fixedWidth) {
// Reset the panel and list dimensions. New dimensions are calculated when
// a new set of items is added to the autocomplete popup.
this._list.width = "";
this._list.height = "";
this._panel.width = "";
this._panel.height = "";
this._panel.top = "";
this._panel.left = "";
}
},
/**
@ -225,7 +311,7 @@ AutocompletePopup.prototype = {
*/
set selectedIndex(aIndex) {
this._list.selectedIndex = aIndex;
if (this._list.ensureIndexIsVisible) {
if (this.isOpen && this._list.ensureIndexIsVisible) {
this._list.ensureIndexIsVisible(this._list.selectedIndex);
}
},
@ -247,23 +333,51 @@ AutocompletePopup.prototype = {
*/
set selectedItem(aItem) {
this._list.selectedItem = this._findListItem(aItem);
this._list.ensureIndexIsVisible(this._list.selectedIndex);
if (this.isOpen) {
this._list.ensureIndexIsVisible(this._list.selectedIndex);
}
},
/**
* Append an item into the autocomplete list.
*
* @param object aItem
* The item you want appended to the list. The object must have a
* "label" property which is used as the displayed value.
* The item you want appended to the list.
* The item object can have the following properties:
* - label {String} Property which is used as the displayed value.
* - preLabel {String} [Optional] The String that will be displayed
* before the label indicating that this is the already
* present text in the input box, and label is the text
* that will be auto completed. When this property is
* present, |preLabel.length| starting characters will be
* removed from label.
* - count {Number} [Optional] The number to represent the count of
* autocompleted label.
*/
appendItem: function AP_appendItem(aItem)
{
let description = this._document.createElementNS(XUL_NS, "description");
description.textContent = aItem.label;
let listItem = this._document.createElementNS(XUL_NS, "richlistitem");
listItem.appendChild(description);
if (this.direction) {
listItem.setAttribute("dir", this.direction);
}
let label = this._document.createElementNS(XUL_NS, "label");
label.setAttribute("value", aItem.label);
label.setAttribute("class", "autocomplete-value");
if (aItem.preLabel) {
let preDesc = this._document.createElementNS(XUL_NS, "label");
preDesc.setAttribute("value", aItem.preLabel);
preDesc.setAttribute("class", "initial-value");
listItem.appendChild(preDesc);
label.setAttribute("value", aItem.label.slice(aItem.preLabel.length));
}
listItem.appendChild(label);
if (aItem.count && aItem.count > 1) {
let countDesc = this._document.createElementNS(XUL_NS, "label");
countDesc.setAttribute("value", aItem.count);
countDesc.setAttribute("flex", "1");
countDesc.setAttribute("class", "autocomplete-count");
listItem.appendChild(countDesc);
}
listItem._autocompleteItem = aItem;
this._list.appendChild(listItem);
@ -351,6 +465,14 @@ AutocompletePopup.prototype = {
return this.selectedItem;
},
/**
* Focuses the richlistbox.
*/
focus: function AP_focus()
{
this._list.focus();
},
/**
* Determine the scrollbar width in the current document.
*

View File

@ -13,7 +13,6 @@ EXTRA_JS_MODULES = \
HUDService.jsm \
PropertyPanel.jsm \
NetworkPanel.jsm \
AutocompletePopup.jsm \
WebConsolePanel.jsm \
$(NULL)

View File

@ -41,7 +41,7 @@ function consoleOpened(aHud) {
// toSource unwatch valueOf watch constructor.
is(popup.itemCount, 18, "popup.itemCount is correct");
let sameItems = popup.getItems().map(function(e) {return e.label;});
let sameItems = popup.getItems().reverse().map(function(e) {return e.label;});
ok(sameItems.every(function(prop, index) {
return [
"__defineGetter__",
@ -64,29 +64,31 @@ function consoleOpened(aHud) {
"watch",
][index] === prop}), "getItems returns the items we expect");
is(popup.selectedIndex, -1, "no index is selected");
is(popup.selectedIndex, 17,
"Index of the first item from bottom is selected.");
EventUtils.synthesizeKey("VK_DOWN", {});
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "__defineSetter__", "__defineSetter__ is selected");
is(completeNode.value, prefix + "__defineSetter__",
"completeNode.value holds __defineSetter__");
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
is(completeNode.value, prefix + "valueOf",
"completeNode.value holds valueOf");
EventUtils.synthesizeKey("VK_UP", {});
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
@ -108,7 +110,7 @@ function autocompletePopupHidden()
ok(!popup.isOpen, "popup is not open");
is(inputNode.value, "window.foobarBug585991.__defineGetter__",
is(inputNode.value, "window.foobarBug585991.watch",
"completion was successful after VK_TAB");
ok(!completeNode.value, "completeNode is empty");
@ -120,15 +122,16 @@ function autocompletePopupHidden()
is(popup.itemCount, 18, "popup.itemCount is correct");
is(popup.selectedIndex, -1, "no index is selected");
is(popup.selectedIndex, 17, "First index from bottom is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
@ -168,29 +171,30 @@ function testReturnKey()
is(popup.itemCount, 18, "popup.itemCount is correct");
is(popup.selectedIndex, -1, "no index is selected");
is(popup.selectedIndex, 17, "First index from bottom is selected");
EventUtils.synthesizeKey("VK_DOWN", {});
EventUtils.synthesizeKey("VK_DOWN", {});
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "__defineGetter__", "__defineGetter__ is selected");
is(completeNode.value, prefix + "__defineGetter__",
"completeNode.value holds __defineGetter__");
is(popup.selectedItem.label, "watch", "watch is selected");
is(completeNode.value, prefix + "watch",
"completeNode.value holds watch");
EventUtils.synthesizeKey("VK_DOWN", {});
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "__defineSetter__", "__defineSetter__ is selected");
is(completeNode.value, prefix + "__defineSetter__",
"completeNode.value holds __defineSetter__");
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
is(completeNode.value, prefix + "valueOf",
"completeNode.value holds valueOf");
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.__defineSetter__",
is(inputNode.value, "window.foobarBug585991.valueOf",
"completion was successful after VK_RETURN");
ok(!completeNode.value, "completeNode is empty");

View File

@ -40,8 +40,9 @@ function consoleOpened(HUD) {
return aItem === items[aIndex];
}), true, "getItems returns back the same items");
is(popup.selectedIndex, -1, "no index is selected");
ok(!popup.selectedItem, "no item is selected");
is(popup.selectedIndex, 2,
"Index of the first item from bottom is selected.");
is(popup.selectedItem, items[2], "First item from bottom is selected");
popup.selectedIndex = 1;

View File

@ -66,21 +66,21 @@ function testCompletion(hud) {
yield;
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
is(jsterm.completeNode.value, "", "'document.getElem' completion");
// Test pressing tab another time.
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
yield;
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entsByClassName", "'document.getElem' another tab completion");
is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' another tab completion");
// Test pressing shift_tab.
jsterm.complete(jsterm.COMPLETE_BACKWARD, testNext);
yield;
is(input.value, "document.getElem", "'document.getElem' untab completion");
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
is(jsterm.completeNode.value, "", "'document.getElem' completion");
jsterm.clearOutput();

View File

@ -29,7 +29,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetworkPanel",
"resource:///modules/NetworkPanel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
"resource:///modules/AutocompletePopup.jsm");
"resource:///modules/devtools/AutocompletePopup.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
@ -2731,9 +2731,18 @@ JSTerm.prototype = {
init: function JST_init()
{
let chromeDocument = this.hud.owner.chromeDocument;
this.autocompletePopup = new AutocompletePopup(chromeDocument);
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
let autocompleteOptions = {
onSelect: this.onAutocompleteSelect.bind(this),
onClick: this.acceptProposedCompletion.bind(this),
panelId: "webConsole_autocompletePopup",
listBoxId: "webConsole_autocompletePopupListBox",
position: "before_start",
theme: "light",
direction: "ltr",
autoSelect: true
};
this.autocompletePopup = new AutocompletePopup(chromeDocument,
autocompleteOptions);
let doc = this.hud.document;
this.completeNode = doc.querySelector(".jsterm-complete-node");
@ -3479,13 +3488,14 @@ JSTerm.prototype = {
}
let matches = aMessage.matches;
let lastPart = aMessage.matchProp;
if (!matches.length) {
this.clearCompletion();
return;
}
let items = matches.map(function(aMatch) {
return { label: aMatch };
let items = matches.reverse().map(function(aMatch) {
return { preLabel: lastPart, label: aMatch };
});
let popup = this.autocompletePopup;
@ -3494,7 +3504,7 @@ JSTerm.prototype = {
let completionType = this.lastCompletion.completionType;
this.lastCompletion = {
value: inputValue,
matchProp: aMessage.matchProp,
matchProp: lastPart,
};
if (items.length > 1 && !popup.isOpen) {

View File

@ -110,10 +110,6 @@ scratchpad.linkText=Shift+RETURN - Open in Scratchpad
# string
gcliterm.instanceLabel=Instance of %S
# LOCALIZATION NOTE (Autocomplete.label):
# The autocomplete popup panel label/title.
Autocomplete.label=Autocomplete popup
# LOCALIZATION NOTE (stacktrace.anonymousFunction):
# This string is used to display JavaScript functions that have no given name -
# they are said to be anonymous. See stacktrace.outputMessage.

View File

@ -3,6 +3,10 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
%browserDTD;
]>
<bindings
xmlns="http://www.mozilla.org/xbl"
@ -21,7 +25,8 @@
<xul:box anonid="selection" class="documenttab-crop"/>
<xul:box anonid="selection" class="documenttab-selection"/>
<xul:button anonid="close" class="documenttab-close" observes="bcast_urlbarState" end="0" top="0"
onclick="event.stopPropagation(); document.getBindingParent(this)._onClose()"/>
onclick="event.stopPropagation(); document.getBindingParent(this)._onClose()"
label="&closetab.label;"/>
</xul:stack>
</content>

View File

@ -404,10 +404,10 @@
<button label="&sync.connect;" oncommand="WeaveGlue.tryConnect();" />
</setting>
<setting id="sync-connected" class="setting-group" title="&sync.connected;" type="control" collapsed="true">
<button id="sync-pairdevice" label="&sync.pair.title;" oncommand="SyncPairDevice.open();" />
<button id="sync-pairdevice" label="&sync.pair.button;" oncommand="SyncPairDevice.open();" />
</setting>
<setting id="sync-sync" class="setting-subgroup" type="control" collapsed="true">
<button id="sync-syncButton" label="&sync.syncNow;" oncommand="WeaveGlue.sync();"/>
<button id="sync-syncButton" label="&sync.syncNow2;" oncommand="WeaveGlue.sync();"/>
</setting>
<setting id="sync-device" class="setting-subgroup" type="string" title="&sync.deviceName;" onchange="WeaveGlue.changeName(this);" collapsed="true"/>
<setting id="sync-disconnect" class="setting-subgroup" type="control" collapsed="true">
@ -457,7 +457,7 @@
</hbox>
<vbox id="syncsetup-simple" class="syncsetup-page" flex="1">
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
<description class="syncsetup-desc" flex="1">&sync.setup.pair;</description>
<description class="syncsetup-desc" flex="1">&sync.setup.pair2;</description>
<description class="link" flex="1" onclick="WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
<separator/>
<vbox flex="1" pack="center" align="start">
@ -514,7 +514,7 @@
</hbox>
<vbox id="syncpair-simple" class="syncsetup-page" flex="1">
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
<description class="syncsetup-desc" flex="1">&sync.pair.description;</description>
<description class="syncsetup-desc" flex="1">&sync.pair.description2;</description>
<description class="link" flex="1" onclick="SyncPairDevice.close(); WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
<separator/>
<vbox align="center" flex="1">
@ -621,10 +621,10 @@
<!-- App bar: 'more' button context menu -->
<richlistitem id="context-findinpage" type="find-in-page" onclick="ContextCommands.findInPage();">
<label value="&appbarFindInPage.label;"/>
<label value="&appbarFindInPage2.label;"/>
</richlistitem>
<richlistitem id="context-viewondesktop" type="view-on-desktop" onclick="ContextCommands.viewOnDesktop();">
<label value="&appbarViewOnDesktop.label;"/>
<label value="&appbarViewOnDesktop2.label;"/>
</richlistitem>
</richlistbox>
</vbox>

View File

@ -515,7 +515,7 @@ let WeaveGlue = {
message = message.replace("#1", brandName);
message = message.replace("#2", Services.appinfo.version);
let title = this._bundle.GetStringFromName("sync.update.title")
let button = this._bundle.GetStringFromName("sync.update.button")
let button = this._bundle.GetStringFromName("sync.update.learnmore")
let close = this._bundle.GetStringFromName("sync.update.close")
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +

View File

@ -54,7 +54,7 @@ LoginManagerPrompter.prototype = {
var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
this.__strBundle = bunService.createBundle(
"chrome://passwordmgr/locale/passwordmgr.properties");
"chrome://browser/locale/passwordmgr.properties");
if (!this.__strBundle)
throw "String bundle for Login Manager not present!";
}
@ -337,7 +337,7 @@ LoginManagerPrompter.prototype = {
var changeButtonAccessKey =
this._getLocalizedString("notifyBarChangeButtonAccessKey");
var dontChangeButtonText =
this._getLocalizedString("notifyBarDontChangeButtonText");
this._getLocalizedString("notifyBarDontChangeButtonText2");
var dontChangeButtonAccessKey =
this._getLocalizedString("notifyBarDontChangeButtonAccessKey");

View File

@ -907,7 +907,7 @@ let PromptUtils = {
};
XPCOMUtils.defineLazyGetter(PromptUtils, "passwdBundle", function () {
return Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
return Services.strings.createBundle("chrome://browser/locale/passwordmgr.properties");
});
XPCOMUtils.defineLazyGetter(PromptUtils, "bundle", function () {

View File

@ -7,13 +7,11 @@
<!ENTITY back.label "Back">
<!ENTITY forward.label "Forward">
<!ENTITY showTabs.label "Show Tabs">
<!ENTITY newtab.label "New Tab">
<!ENTITY closetab.label "Close Tab">
<!ENTITY undoclosetab.label "Undo Close Tab">
<!ENTITY appbarFindInPage.label "Find in Page">
<!ENTITY appbarViewOnDesktop.label "View on Desktop">
<!ENTITY appbarFindInPage2.label "Find in page">
<!ENTITY appbarViewOnDesktop2.label "View on desktop">
<!ENTITY startTopSitesHeader.label "Top Sites">
<!ENTITY startBookmarksHeader.label "Bookmarks">

View File

@ -38,16 +38,16 @@ alertDownloadsNoSpace=Not enough storage space
# Popup Blocker
popupWarning=%S prevented this site from opening a pop-up window.
popupWarningMultiple=%S prevented this site from opening %S pop-up windows.
popupButtonAllowOnce2=Allow Once
popupButtonAlwaysAllow3=Always Allow
popupButtonNeverWarn3=Never Allow
popupButtonAllowOnce2=Allow once
popupButtonAlwaysAllow3=Always allow
popupButtonNeverWarn3=Never allow
# ContentPermissionsPrompt
contentPermissions.alwaysForSite=Always for this Site
contentPermissions.neverForSite=Never for this Site
contentPermissions.alwaysForSite=Always for this site
contentPermissions.neverForSite=Never for this site
# Geolocation UI
geolocation2.allow=Share Location
geolocation2.allow=Share location
geolocation2.wantsTo=Share your location with %S?
geolocation.learnMore=Learn more…
@ -64,9 +64,6 @@ offlineApps.wantsTo=%S wants to store data on your device for offline use.
indexedDBQuota.allow=Allow
indexedDBQuota.wantsTo=%S wants to store a lot of data on your device for offline use.
# Bookmark List
bookmarkList.desktop=Desktop Bookmarks
# Closing Tabs
tabs.closeWarningTitle=Confirm close
@ -82,8 +79,6 @@ tabs.emptyTabTitle=New Tab
# Open Search
opensearch.search=Search: %S
# Open in Another App
# LOCALIZATION NOTE: openinapp.specific is the text displayed if there is a single external app
# %S is the name of the app, like "YouTube" or "Picassa"
openinapp.specific=Open in %S App
openinapp.general=Open in Another App
# Clear Private Data
clearPrivateData.title=Clear Private Data
clearPrivateData.message=Delete your browsing history and settings, including passwords and cookies?

View File

@ -2,7 +2,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
rememberValue = Use Password Manager to remember this value.
rememberPassword = Use Password Manager to remember this password.
savePasswordTitle = Confirm
# 1st string is product name, 2nd is the username for the login, 3rd is the
@ -10,29 +9,15 @@ savePasswordTitle = Confirm
saveLoginText = Do you want %1$S to remember the password for "%2$S" on %3$S?
# 1st string is product name, 2nd is the login's hostname
saveLoginTextNoUsername = Do you want %1$S to remember this password on %2$S?
promptNotNowButtonText = Not Now
promptNeverForSiteButtonText = Never
notifyBarNotForThisSiteButtonText = Not for this Site
notifyBarNotForThisSiteButtonText = Not for this site
notifyBarNotForThisSiteButtonAccessKey =
promptRememberButtonText = Remember
notifyBarRememberPasswordButtonText = Remember Password
notifyBarRememberPasswordButtonText = Remember password
notifyBarRememberPasswordButtonAccessKey =
passwordChangeTitle = Confirm Password Change
passwordChangeText = Would you like to change the stored password for %S?
passwordChangeTextNoUser = Would you like to change the stored password for this login?
notifyBarChangeButtonText = Change
notifyBarChangeButtonAccessKey =
notifyBarDontChangeButtonText = Don't Change
notifyBarDontChangeButtonText2 = Don't change
notifyBarDontChangeButtonAccessKey =
userSelectText = Please confirm which user you are changing the password for
hidePasswords=Hide Passwords
hidePasswordsAccessKey=P
showPasswords=Show Passwords
showPasswordsAccessKey=P
noMasterPasswordPrompt=Are you sure you wish to show your passwords?
removeAllPasswordsPrompt=Are you sure you wish to remove all passwords?
removeAllPasswordsTitle=Remove all passwords
loginsSpielAll=Passwords for the following sites are stored on your computer:
loginsSpielFiltered=The following passwords match your search:
username=Username
password=Password

View File

@ -9,10 +9,10 @@
<!ENTITY sync.connected "Connected">
<!ENTITY sync.deviceName "This device">
<!ENTITY sync.disconnect "Disconnect">
<!ENTITY sync.syncNow "Sync Now">
<!ENTITY sync.syncNow2 "Sync now">
<!ENTITY sync.setup.title "Connect to Sync">
<!ENTITY sync.setup.pair "To activate, select &#x0022;Pair a Device&#x0022; on your other device.">
<!ENTITY sync.setup.pair2 "To activate, select &#x0022;Pair a device&#x0022; on your other device.">
<!ENTITY sync.fallback "I'm not near my computer…">
<!ENTITY sync.setup.manual "Enter your Sync account information">
<!ENTITY sync.account "Account Name">
@ -26,6 +26,7 @@
<!ENTITY sync.setup.waiting2 "Waiting for other device…">
<!ENTITY sync.pair.title "Pair a Device">
<!ENTITY sync.pair.description "To activate your new device, select &#x0022;Set Up Sync&#x0022; on the device.">
<!ENTITY sync.pair.button "Pair a device">
<!ENTITY sync.pair.description2 "To activate your new device, select &#x0022;Set up sync&#x0022; on the device.">
<!ENTITY sync.setup.close "Close">
<!ENTITY sync.setup.waitingdownload "Your data is now being downloaded in the background. You can close this window at any time.">

View File

@ -22,7 +22,7 @@ notificationDisconnect.button=Undo
sync.update.client=#1 #2 is not compatible with the latest version of Firefox Sync. Please update to the latest version.
sync.update.remote=#1 #2 is not compatible with older versions of Firefox Sync. Please update Firefox on your other computer(s).
sync.update.title=Firefox Sync
sync.update.button=Learn More
sync.update.learnmore=Learn more
sync.update.close=Close
sync.setup.error.title=Cannot Setup Sync
sync.setup.error.network=No internet connection available

View File

@ -17,14 +17,13 @@
locale/browser/checkbox.dtd (%chrome/checkbox.dtd)
locale/browser/sync.dtd (%chrome/sync.dtd)
locale/browser/sync.properties (%chrome/sync.properties)
locale/browser/passwordmgr.properties (%chrome/passwordmgr.properties)
locale/browser/prompt.dtd (%chrome/prompt.dtd)
locale/browser/phishing.dtd (%chrome/phishing.dtd)
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/
locale/browser/bookmarks.json (bookmarks.json)
locale/browser/passwordmgr.properties (%overrides/passwordmgr.properties)
% override chrome://passwordmgr/locale/passwordmgr.properties chrome://browser/locale/passwordmgr.properties
#
# Browser jar resources

View File

@ -33,6 +33,7 @@ this.RecentWindow = {
return (!win.closed &&
win.toolbar.visible &&
(!checkPrivacy ||
PrivateBrowsingUtils.permanentPrivateBrowsing ||
PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
}

View File

@ -219,6 +219,10 @@
/* In-tools sidebar */
.devtools-toolbox-side-iframe {
min-width: 465px;
}
.devtools-sidebar-tabs {
-moz-appearance: none;
margin: 0;
@ -360,3 +364,5 @@
.devtools-theme-attrvalue {
color: hsl(24,85%,39%); /* orange */
}
%include ../../shared/devtools/common.inc.css

View File

@ -150,8 +150,10 @@
.devtools-tab {
-moz-appearance: none;
min-width: 88px;
width: 47px;
min-width: 47px;
min-height: 32px;
max-width: 137px;
color: #b6babf;
margin: 0;
padding: 0;
@ -163,25 +165,23 @@
border-right: 1px solid hsla(206,37%,4%,.45);
}
.devtools-tab > .radio-label-center-box > .radio-label-box {
-moz-appearance: none;
.devtools-tab > image {
border: none;
padding: 0 16px;
}
.devtools-tab > .radio-label-center-box >.radio-label-box > .radio-icon {
-moz-margin-end: 6px;
-moz-margin-start: 16px;
opacity: 0.6;
}
.devtools-tab:hover > .radio-label-center-box > .radio-label-box >
.radio-icon {
.devtools-tab > label {
white-space: nowrap;
}
.devtools-tab:hover > image {
opacity: 0.8;
}
.devtools-tab:active > .radio-label-center-box > .radio-label-box > .radio-icon,
.devtools-tab[selected=true] > .radio-label-center-box > .radio-label-box >
.radio-icon {
.devtools-tab:active > image,
.devtools-tab[selected=true] > label {
opacity: 1;
}

View File

@ -233,6 +233,10 @@
/* In-tools sidebar */
.devtools-toolbox-side-iframe {
min-width: 465px;
}
.devtools-sidebar-tabs {
-moz-appearance: none;
margin: 0;
@ -379,3 +383,5 @@
.devtools-theme-attrvalue {
color: hsl(24,85%,39%); /* orange */
}
%include ../../shared/devtools/common.inc.css

View File

@ -139,8 +139,10 @@
.devtools-tab {
-moz-appearance: none;
min-width: 88px;
width: 47px;
min-width: 47px;
min-height: 32px;
max-width: 137px;
color: #b6babf;
margin: 0;
padding: 0;
@ -152,21 +154,22 @@
border-right: 1px solid hsla(206,37%,4%,.45);
}
.devtools-tab > .radio-label-box {
padding: 0 16px;
}
.devtools-tab > .radio-label-box > .radio-icon {
.devtools-tab > image {
-moz-margin-end: 6px;
-moz-margin-start: 16px;
opacity: 0.6;
}
.devtools-tab:hover > .radio-label-box > .radio-icon {
.devtools-tab > label {
white-space: nowrap;
}
.devtools-tab:hover > image {
opacity: 0.8;
}
.devtools-tab:active > .radio-label-box > .radio-icon,
.devtools-tab[selected=true] > .radio-label-box > .radio-icon {
.devtools-tab:active > image,
.devtools-tab[selected=true] > image {
opacity: 1;
}

View File

@ -0,0 +1,85 @@
%if 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/. */
%endif
/* Autocomplete Popup */
/* Dark and light theme */
.devtools-autocomplete-popup {
-moz-appearance: none !important;
border: 1px solid hsl(210,11%,10%);
box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
background-color: transparent;
background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
border-radius: 3px;
%ifdef XP_LINUX
max-height: 32rem;
%else
max-height: 40rem;
%endif
}
.devtools-autocomplete-listbox {
-moz-appearance: none !important;
background-color: transparent;
border-width: 0px !important;
}
.devtools-autocomplete-listbox > richlistitem,
.devtools-autocomplete-listbox > richlistitem[selected] {
width: 100%;
background-color: transparent;
border-radius: 4px;
}
.devtools-autocomplete-listbox.dark-theme > richlistitem[selected],
.devtools-autocomplete-listbox.dark-theme > richlistitem:hover {
background-color: rgba(0,0,0,0.5);
}
.devtools-autocomplete-listbox.dark-theme > richlistitem[selected] > .autocomplete-value,
.devtools-autocomplete-listbox:focus.dark-theme > richlistitem[selected] > .initial-value {
color: hsl(208,100%,60%);
}
.devtools-autocomplete-listbox.dark-theme > richlistitem[selected] > label {
color: #eee;
}
.devtools-autocomplete-listbox.dark-theme > richlistitem > label {
color: #ccc;
}
.devtools-autocomplete-listbox > richlistitem > .initial-value,
.devtools-autocomplete-listbox > richlistitem > .autocomplete-value {
margin: 0;
padding: 1px 0;
}
.devtools-autocomplete-listbox > richlistitem > .autocomplete-count {
text-align: right;
}
/* Rest of the light theme */
.devtools-autocomplete-popup.light-theme {
border: 1px solid hsl(210,24%,90%);
box-shadow: 0 1px 0 hsla(209,29%,90%,.25) inset;
background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
}
.devtools-autocomplete-listbox.light-theme > richlistitem[selected],
.devtools-autocomplete-listbox.light-theme > richlistitem:hover {
background-color: rgba(128,128,128,0.3);
}
.devtools-autocomplete-listbox.light-theme > richlistitem[selected] > .autocomplete-value,
.devtools-autocomplete-listbox:focus.light-theme > richlistitem[selected] > .initial-value {
color: #222;
}
.devtools-autocomplete-listbox.light-theme > richlistitem > label {
color: #666;
}

View File

@ -239,6 +239,10 @@
/* In-tools sidebar */
.devtools-toolbox-side-iframe {
min-width: 465px;
}
.devtools-sidebar-tabs {
-moz-appearance: none;
margin: 0;
@ -388,3 +392,5 @@
.devtools-theme-attrvalue {
color: hsl(24,85%,39%); /* orange */
}
%include ../../shared/devtools/common.inc.css

View File

@ -151,8 +151,10 @@
.devtools-tab {
-moz-appearance: none;
min-width: 88px;
width: 47px;
min-width: 47px;
min-height: 32px;
max-width: 137px;
color: #b6babf;
margin: 0;
padding: 0;
@ -165,22 +167,18 @@
border-right: 1px solid hsla(206,37%,4%,.45);
}
.devtools-tab > .radio-label-box {
border: none;
padding: 0 16px;
}
.devtools-tab > .radio-label-box > .radio-icon {
.devtools-tab > image {
-moz-margin-end: 6px;
-moz-margin-start: 16px;
opacity: 0.6;
}
.devtools-tab:hover > .radio-label-box > .radio-icon {
.devtools-tab:hover > image {
opacity: 0.8;
}
.devtools-tab:active > .radio-label-box > .radio-icon,
.devtools-tab[selected=true] > .radio-label-box > .radio-icon {
.devtools-tab:active > image,
.devtools-tab[selected=true] > image {
opacity: 1;
}

View File

@ -22,4 +22,5 @@ public interface Assert {
// robocop-specific asserts
void ispixel(int actual, int r, int g, int b, String name);
void isnotpixel(int actual, int r, int g, int b, String name);
}

View File

@ -148,6 +148,24 @@ public class FennecMochitestAssert implements Assert {
}
public void ispixel(int actual, int r, int g, int b, String name) {
int aAlpha = ((actual >> 24) & 0xFF);
int aR = ((actual >> 16) & 0xFF);
int aG = ((actual >> 8) & 0xFF);
int aB = (actual & 0xFF);
boolean pass = checkPixel(actual, r, g, b);
ok(pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (pass ? " " : " not") + " close enough to expected rgb(" + r + "," + g + "," + b + ")");
}
public void isnotpixel(int actual, int r, int g, int b, String name) {
int aAlpha = ((actual >> 24) & 0xFF);
int aR = ((actual >> 16) & 0xFF);
int aG = ((actual >> 8) & 0xFF);
int aB = (actual & 0xFF);
boolean pass = checkPixel(actual, r, g, b);
ok(!pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (!pass ? " is" : " is not") + " different enough from rgb(" + r + "," + g + "," + b + ")");
}
private boolean checkPixel(int actual, int r, int g, int b) {
// When we read GL pixels the GPU has already processed them and they
// are usually off by a little bit. For example a CSS-color pixel of color #64FFF5
// was turned into #63FFF7 when it came out of glReadPixels. So in order to compare
@ -160,10 +178,14 @@ public class FennecMochitestAssert implements Assert {
int aG = ((actual >> 8) & 0xFF);
int aB = (actual & 0xFF);
boolean pass = (aAlpha == 0xFF) /* alpha */
&& (Math.abs(aR - r) <= 8) /* red */
&& (Math.abs(aG - g) <= 8) /* green */
&& (Math.abs(aB - b) <= 8); /* blue */
ok(pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (pass ? " " : " not") + " close enough to expected rgb(" + r + "," + g + "," + b + ")");
&& (Math.abs(aR - r) <= 8) /* red */
&& (Math.abs(aG - g) <= 8) /* green */
&& (Math.abs(aB - b) <= 8); /* blue */
if (pass) {
return true;
} else {
return false;
}
}
public void todo(boolean condition, String name, String diag) {

View File

@ -53,6 +53,10 @@ public class FennecTalosAssert implements Assert {
throw new UnsupportedOperationException();
}
public void isnotpixel(int actual, int r, int g, int b, String name) {
throw new UnsupportedOperationException();
}
public void todo(boolean condition, String name, String diag) {
throw new UnsupportedOperationException();
}

View File

@ -120,13 +120,6 @@ template <class ErrorResult>
class AudioEventTimeline
{
public:
// This constructor should only be used for objects which are meant to be
// copied from other properly constructed objects.
AudioEventTimeline()
: mValue(0.f)
{
}
explicit AudioEventTimeline(float aDefaultValue)
: mValue(aDefaultValue)
{

View File

@ -21,6 +21,7 @@ class nsXPCClassInfo;
#undef GetCurrentTime
#endif
// X11 has a #define for CurrentTime. Unbelievable :-(.
// See content/media/webaudio/AudioContext.h for more fun!
#ifdef CurrentTime
#undef CurrentTime
#endif

View File

@ -191,5 +191,11 @@ AudioContext::DestinationStream() const
return Destination()->Stream();
}
double
AudioContext::CurrentTime() const
{
return MediaTimeToSeconds(Destination()->Stream()->GetCurrentTime());
}
}
}

View File

@ -21,6 +21,12 @@
#include "MediaStreamGraph.h"
#include "nsIDOMWindow.h"
// X11 has a #define for CurrentTime. Unbelievable :-(.
// See content/media/DOMMediaStream.h for more fun!
#ifdef CurrentTime
#undef CurrentTime
#endif
struct JSContext;
class JSObject;
class nsIDOMWindow;
@ -78,6 +84,8 @@ public:
return float(IdealAudioRate());
}
double CurrentTime() const;
AudioListener* Listener();
already_AddRefed<AudioBufferSourceNode> CreateBufferSource();

View File

@ -46,6 +46,8 @@ public:
explicit GainNodeEngine(AudioDestinationNode* aDestination)
: mSource(nullptr)
, mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
// Keep the default value in sync with the default value in GainNode::GainNode.
, mGain(1.f)
{
}

View File

@ -21,6 +21,7 @@ MOCHITEST_FILES := \
test_AudioListener.html \
test_badConnect.html \
test_biquadFilterNode.html \
test_currentTime.html \
test_delayNode.html \
test_decodeAudioData.html \
test_dynamicsCompressorNode.html \

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test AudioContext.currentTime</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
var ac = new AudioContext();
is(ac.currentTime, 0, "AudioContext.currentTime should be 0 initially");
setTimeout(function() {
ok(ac.currentTime > 0, "AudioContext.currentTime should have increased by now");
SpecialPowers.clearUserPref("media.webaudio.enabled");
SimpleTest.finish();
}, 100);
});
</script>
</pre>
</body>
</html>

View File

@ -2192,7 +2192,7 @@ nsDOMWindowUtils::GetLayerManagerType(nsAString& aType)
if (!widget)
return NS_ERROR_FAILURE;
LayerManager *mgr = widget->GetLayerManager();
LayerManager *mgr = widget->GetLayerManager(nsIWidget::LAYER_MANAGER_PERSISTENT);
if (!mgr)
return NS_ERROR_FAILURE;

View File

@ -10094,11 +10094,11 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout)
TimeStamp deadline;
if (aTimeout && aTimeout->mWhen > now) {
// The OS timer fired early (yikes!), and possibly out of order
// too. Set |deadline| to be the time when the OS timer *should*
// have fired so that any timers that *should* have fired before
// aTimeout *will* be fired now. This happens most of the time on
// Win2k.
// The OS timer fired early (which can happen due to the timers
// having lower precision than TimeStamp does). Set |deadline| to
// be the time when the OS timer *should* have fired so that any
// timers that *should* have fired before aTimeout *will* be fired
// now.
deadline = aTimeout->mWhen;
} else {

View File

@ -507,6 +507,7 @@ class CGHeaders(CGWrapper):
# Now find all the things we'll need as arguments because we
# need to wrap or unwrap them.
bindingHeaders = set()
declareIncludes = set(declareIncludes)
def addHeadersForType(t, descriptor=None, dictionary=None):
"""
Add the relevant headers for this type. We use descriptor and
@ -529,7 +530,19 @@ class CGHeaders(CGWrapper):
typeDesc = p.getDescriptor(unrolled.inner.identifier.name)
except NoSuchDescriptorError:
continue
implementationIncludes.add(typeDesc.headerFile)
if dictionary:
# Dictionaries with interface members rely on the
# actual class definition of that interface member
# being visible in the binding header, because they
# store them in nsRefPtr and have inline
# constructors/destructors.
#
# XXXbz maybe dictionaries with interface members
# should just have out-of-line constructors and
# destructors?
declareIncludes.add(typeDesc.headerFile)
else:
implementationIncludes.add(typeDesc.headerFile)
bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface))
elif unrolled.isDictionary():
bindingHeaders.add(self.getDeclarationFilename(unrolled.inner))
@ -556,7 +569,6 @@ class CGHeaders(CGWrapper):
# Strip out the function name and convert "::" to "/"
bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
declareIncludes = set(declareIncludes)
for d in dictionaries:
if d.parent:
declareIncludes.add(self.getDeclarationFilename(d.parent))

View File

@ -1082,6 +1082,10 @@ class IDLDictionary(IDLObjectWithScope):
return (False, None)
for member in self.members:
if member.type.isDictionary() and member.type.nullable():
raise WebIDLError("Dictionary %s has member with nullable "
"dictionary type" % self.identifier.name,
[member.location])
(contains, locations) = typeContainsDictionary(member.type, self)
if contains:
raise WebIDLError("Dictionary %s has member with itself as type." %

View File

@ -425,3 +425,20 @@ def WebIDLTest(parser, harness):
harness.ok(threw, "Member type must not be a Dictionary, one of whose "
"members or inherited members has a type that includes "
"its Dictionary.")
parser = parser.reset();
threw = False
try:
parser.parse("""
dictionary Foo {
};
dictionary Bar {
Foo? d;
};
""")
results = parser.finish()
except:
threw = True
harness.ok(threw, "Member type must not be a nullable dictionary")

View File

@ -63,7 +63,7 @@ IDPProvisioningContext.prototype = {
doError: function(msg) {
log("Provisioning ERROR: " + msg);
},
}
};
function IDPAuthenticationContext(aID, aOrigin, aTargetMM) {
@ -85,7 +85,7 @@ IDPAuthenticationContext.prototype = {
doError: function IDPAC_doError(msg) {
log("Authentication ERROR: " + msg);
},
}
};
function RPWatchContext(aOptions, aTargetMM) {
@ -152,11 +152,14 @@ this.DOMIdentity = {
case "Identity:RP:Watch":
this._watch(msg, targetMM);
break;
case "Identity:RP:Unwatch":
this._unwatch(msg, targetMM);
break;
case "Identity:RP:Request":
this._request(msg);
this._request(msg, targetMM);
break;
case "Identity:RP:Logout":
this._logout(msg);
this._logout(msg, targetMM);
break;
// IDP
case "Identity:IDP:BeginProvisioning":
@ -180,6 +183,12 @@ this.DOMIdentity = {
case "Identity:IDP:AuthenticationFailure":
this._authenticationFailure(msg);
break;
case "child-process-shutdown":
// we receive child-process-shutdown if the appliction crashes,
// including if it is crashed by the OS (killed for out-of-memory,
// for example)
this._childProcessShutdown(targetMM);
break;
}
},
@ -199,7 +208,9 @@ this.DOMIdentity = {
"Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair",
"Identity:IDP:BeginAuthentication",
"Identity:IDP:CompleteAuthentication",
"Identity:IDP:AuthenticationFailure"],
"Identity:IDP:AuthenticationFailure",
"Identity:RP:Unwatch",
"child-process-shutdown"],
// Private.
_init: function DOMIdentity__init() {
@ -239,6 +250,10 @@ this.DOMIdentity = {
IdentityService.RP.watch(context);
},
_unwatch: function DOMIdentity_unwatch(message, targetMM) {
IdentityService.RP.unwatch(message.id, targetMM);
},
_request: function DOMIdentity__request(message) {
IdentityService.RP.request(message.id, message);
},
@ -247,6 +262,10 @@ this.DOMIdentity = {
IdentityService.RP.logout(message.id, message.origin, message);
},
_childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) {
IdentityService.RP.childProcessShutdown(targetMM);
},
_beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
let context = new IDPProvisioningContext(message.id, message.origin,
targetMM);
@ -277,7 +296,7 @@ this.DOMIdentity = {
_authenticationFailure: function DOMIdentity__authenticationFailure(message) {
IdentityService.IDP.cancelAuthentication(message.id);
},
}
};
// Object is initialized by nsIDService.js

View File

@ -49,7 +49,7 @@ nsDOMIdentity.prototype = {
// Authentication
beginAuthentication: 'r',
completeAuthentication: 'r',
raiseAuthenticationFailure: 'r',
raiseAuthenticationFailure: 'r'
},
// require native events unless syntheticEventsOk is set
@ -405,7 +405,7 @@ nsDOMIdentity.prototype = {
case "Identity:RP:Watch:OnLogin":
// Do we have a watcher?
if (!this._rpWatcher) {
dump("WARNING: Received OnLogin message, but there is no RP watcher\n");
this._log("WARNING: Received OnLogin message, but there is no RP watcher");
return;
}
@ -420,7 +420,7 @@ nsDOMIdentity.prototype = {
case "Identity:RP:Watch:OnLogout":
// Do we have a watcher?
if (!this._rpWatcher) {
dump("WARNING: Received OnLogout message, but there is no RP watcher\n");
this._log("WARNING: Received OnLogout message, but there is no RP watcher");
return;
}
@ -431,7 +431,7 @@ nsDOMIdentity.prototype = {
case "Identity:RP:Watch:OnReady":
// Do we have a watcher?
if (!this._rpWatcher) {
dump("WARNING: Received OnReady message, but there is no RP watcher\n");
this._log("WARNING: Received OnReady message, but there is no RP watcher");
return;
}
@ -442,7 +442,7 @@ nsDOMIdentity.prototype = {
case "Identity:RP:Watch:OnCancel":
// Do we have a watcher?
if (!this._rpWatcher) {
dump("WARNING: Received OnCancel message, but there is no RP watcher\n");
this._log("WARNING: Received OnCancel message, but there is no RP watcher");
return;
}
@ -524,6 +524,14 @@ nsDOMIdentity.prototype = {
return message;
},
uninit: function DOMIdentity_uninit() {
this._log("nsDOMIdentity uninit()");
this._identityInternal._mm.sendAsyncMessage(
"Identity:RP:Unwatch",
{ id: this._id }
);
}
};
/**
@ -550,6 +558,8 @@ nsDOMIdentityInternal.prototype = {
return;
}
this._identity.uninit();
Services.obs.removeObserver(this, "inner-window-destroyed");
this._identity._initializeState();
this._identity = null;
@ -601,11 +611,11 @@ nsDOMIdentityInternal.prototype = {
"Identity:RP:Watch:OnCancel",
"Identity:IDP:CallBeginProvisioningCallback",
"Identity:IDP:CallGenKeyPairCallback",
"Identity:IDP:CallBeginAuthenticationCallback",
"Identity:IDP:CallBeginAuthenticationCallback"
];
this._messages.forEach((function(msgName) {
this._messages.forEach(function(msgName) {
this._mm.addMessageListener(msgName, this);
}).bind(this));
}, this);
// Setup observers so we can remove message listeners.
Services.obs.addObserver(this, "inner-window-destroyed", false);

View File

@ -622,19 +622,22 @@ StackBasedEventTarget::QueryInterface(REFNSIID aIID,
}
NS_IMETHODIMP
MainThreadEventTarget::Dispatch(nsIRunnable* aRunnable,
uint32_t aFlags)
ImmediateRunEventTarget::Dispatch(nsIRunnable* aRunnable,
uint32_t aFlags)
{
NS_ASSERTION(aRunnable, "Null pointer!");
nsCOMPtr<nsIRunnable> runnable = aRunnable;
return NS_DispatchToMainThread(aRunnable, aFlags);
nsCOMPtr<nsIRunnable> runnable(aRunnable);
DebugOnly<nsresult> rv =
runnable->Run();
MOZ_ASSERT(NS_SUCCEEDED(rv));
return NS_OK;
}
NS_IMETHODIMP
MainThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
ImmediateRunEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
{
*aIsOnCurrentThread = NS_IsMainThread();
*aIsOnCurrentThread = true;
return NS_OK;
}

View File

@ -248,7 +248,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
};
class MainThreadEventTarget : public StackBasedEventTarget
class ImmediateRunEventTarget : public StackBasedEventTarget
{
public:
NS_DECL_NSIEVENTTARGET

View File

@ -386,7 +386,7 @@ IndexedDBDatabaseChild::RecvSuccess(
openHelper = new IPCOpenDatabaseHelper(mDatabase, request);
}
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(openHelper->Dispatch(&target))) {
NS_WARNING("Dispatch of IPCOpenDatabaseHelper failed!");
return false;
@ -418,7 +418,7 @@ IndexedDBDatabaseChild::RecvError(const nsresult& aRv)
openHelper->SetError(aRv);
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(openHelper->Dispatch(&target))) {
NS_WARNING("Dispatch of IPCOpenDatabaseHelper failed!");
return false;
@ -436,7 +436,7 @@ IndexedDBDatabaseChild::RecvBlocked(const uint64_t& aOldVersion)
nsCOMPtr<nsIRunnable> runnable =
IDBVersionChangeEvent::CreateBlockedRunnable(mRequest, aOldVersion, mVersion);
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of blocked event failed!");
}
@ -453,7 +453,7 @@ IndexedDBDatabaseChild::RecvVersionChange(const uint64_t& aOldVersion,
nsCOMPtr<nsIRunnable> runnable =
new VersionChangeRunnable(mDatabase, aOldVersion, aNewVersion);
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of versionchange event failed!");
}
@ -521,14 +521,14 @@ IndexedDBDatabaseChild::RecvPIndexedDBTransactionConstructor(
mDatabase->EnterSetVersionTransaction();
mDatabase->mPreviousDatabaseInfo->version = oldVersion;
MainThreadEventTarget target;
actor->SetTransaction(transaction);
ImmediateRunEventTarget target;
if (NS_FAILED(versionHelper->Dispatch(&target))) {
NS_WARNING("Dispatch of IPCSetVersionHelper failed!");
return false;
}
actor->SetTransaction(transaction);
mOpenHelper = helper.forget();
return true;
}
@ -604,7 +604,7 @@ IndexedDBTransactionChild::FireCompleteEvent(nsresult aRv)
nsRefPtr<CommitHelper> helper = new CommitHelper(transaction, aRv);
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(target.Dispatch(helper, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of CommitHelper failed!");
}
@ -1240,7 +1240,7 @@ IndexedDBDeleteDatabaseRequestChild::Recv__delete__(const nsresult& aRv)
helper->SetError(aRv);
}
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(helper->Dispatch(&target))) {
NS_WARNING("Dispatch of IPCSetVersionHelper failed!");
return false;
@ -1259,7 +1259,7 @@ IndexedDBDeleteDatabaseRequestChild::RecvBlocked(
IDBVersionChangeEvent::CreateBlockedRunnable(mOpenRequest,
aCurrentVersion, 0);
MainThreadEventTarget target;
ImmediateRunEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of blocked event failed!");
}

View File

@ -18,6 +18,7 @@ interface AudioContext {
readonly attribute AudioDestinationNode destination;
readonly attribute float sampleRate;
readonly attribute double currentTime;
readonly attribute AudioListener listener;
[Creator, Throws]

View File

@ -13,6 +13,7 @@ interface DummyInterface {
RTCConfiguration rtcConfiguration();
CFStateChangeEventDict cfstateChangeEvent();
USSDReceivedEventDict ussdReceivedEvent();
InspectorRGBTriple rgbTriple();
};
interface DummyInterfaceWorkers {

View File

@ -0,0 +1,16 @@
/* -*- Mode: IDL; 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/.
*/
dictionary InspectorRGBTriple {
/*
* NOTE: Using octet for RGB components is not generally OK, because
* they can be outside the 0-255 range, but for backwards-compatible
* named colors (which is what we use this dictionary for) the 0-255
* assumption is fine.
*/
octet r = 0;
octet g = 0;
octet b = 0;
};

View File

@ -119,6 +119,7 @@ webidl_files = \
HTMLUListElement.webidl \
IDBVersionChangeEvent.webidl \
ImageData.webidl \
InspectorUtils.webidl \
LinkStyle.webidl \
LocalMediaStream.webidl \
Location.webidl \

View File

@ -288,3 +288,15 @@ NS_HSL2RGB(float h, float s, float l)
b = uint8_t(255 * HSL_HueToRGB(m1, m2, h - 1.0f/3.0f));
return NS_RGB(r, g, b);
}
NS_GFX_(const char*)
NS_RGBToColorName(nscolor aColor)
{
for (size_t idx = 0; idx < ArrayLength(kColors); ++idx) {
if (kColors[idx] == aColor) {
return kColorNames[idx];
}
}
return nullptr;
}

View File

@ -70,4 +70,11 @@ NS_GFX_(bool) NS_ColorNameToRGB(const nsAString& aBuf, nscolor* aResult);
// the float parameters are all expected to be in the range 0-1
NS_GFX_(nscolor) NS_HSL2RGB(float h, float s, float l);
// Return a color name for the given nscolor. If there is no color
// name for it, returns null. If there are multiple possible color
// names for the given color, the first one in nsColorNameList.h
// (which is generally the first one in alphabetical order) will be
// returned.
NS_GFX_(const char*) NS_RGBToColorName(nscolor aColor);
#endif /* nsColor_h___ */

View File

@ -86,7 +86,8 @@ public:
void ReplaceFontEntry(gfxFontEntry *aOldFontEntry,
gfxFontEntry *aNewFontEntry) {
uint32_t numFonts = mAvailableFonts.Length();
for (uint32_t i = 0; i < numFonts; i++) {
uint32_t i;
for (i = 0; i < numFonts; i++) {
gfxFontEntry *fe = mAvailableFonts[i];
if (fe == aOldFontEntry) {
// note that this may delete aOldFontEntry, if there's no
@ -96,6 +97,7 @@ public:
break;
}
}
NS_ASSERTION(i < numFonts, "font entry not found in family!");
ResetCharacterMap();
}

View File

@ -12,6 +12,7 @@
#include "jsapi.h"
#include "jscntxt.h"
#include "jsgc.h"
#include "jsonparser.h"
#include "jsprf.h"
#include "jswatchpoint.h"
@ -620,6 +621,10 @@ AutoGCRooter::trace(JSTracer *trc)
MarkValueUnbarriered(trc, &p->get(), "js::AutoWrapperVector.vector");
return;
}
case JSONPARSER:
static_cast<js::JSONParser *>(this)->trace(trc);
return;
}
JS_ASSERT(tag_ >= 0);

View File

@ -142,7 +142,8 @@ class JS_PUBLIC_API(AutoGCRooter) {
WRAPPER = -31, /* js::AutoWrapperRooter */
OBJOBJHASHMAP=-32, /* js::AutoObjectObjectHashMap */
OBJU32HASHMAP=-33, /* js::AutoObjectUnsigned32HashMap */
OBJHASHSET = -34 /* js::AutoObjectHashSet */
OBJHASHSET = -34, /* js::AutoObjectHashSet */
JSONPARSER = -35 /* js::JSONParser */
};
private:

View File

@ -3202,7 +3202,7 @@ struct types::ArrayTableKey
};
void
TypeCompartment::fixArrayType(JSContext *cx, HandleObject obj)
TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
{
AutoEnterAnalysis enter(cx);
@ -3285,31 +3285,32 @@ TypeCompartment::fixArrayType(JSContext *cx, HandleObject obj)
*/
struct types::ObjectTableKey
{
jsid *ids;
uint32_t nslots;
jsid *properties;
uint32_t nproperties;
uint32_t nfixed;
TaggedProto proto;
typedef JSObject * Lookup;
struct Lookup {
IdValuePair *properties;
uint32_t nproperties;
uint32_t nfixed;
static inline uint32_t hash(JSObject *obj) {
return (uint32_t) (JSID_BITS(obj->lastProperty()->propid().get()) ^
obj->slotSpan() ^ obj->numFixedSlots() ^
((uint32_t)obj->getTaggedProto().toWord() >> 2));
Lookup(IdValuePair *properties, uint32_t nproperties, uint32_t nfixed)
: properties(properties), nproperties(nproperties), nfixed(nfixed)
{}
};
static inline HashNumber hash(const Lookup &lookup) {
return (HashNumber) (JSID_BITS(lookup.properties[lookup.nproperties - 1].id) ^
lookup.nproperties ^
lookup.nfixed);
}
static inline bool match(const ObjectTableKey &v, RawObject obj) {
if (obj->slotSpan() != v.nslots ||
obj->numFixedSlots() != v.nfixed ||
obj->getTaggedProto() != v.proto) {
static inline bool match(const ObjectTableKey &v, const Lookup &lookup) {
if (lookup.nproperties != v.nproperties || lookup.nfixed != v.nfixed)
return false;
}
RawShape shape = obj->lastProperty();
obj = NULL;
while (!shape->isEmptyShape()) {
if (shape->propid() != v.ids[shape->slot()])
for (size_t i = 0; i < lookup.nproperties; i++) {
if (lookup.properties[i].id != v.properties[i])
return false;
shape = shape->previous();
}
return true;
}
@ -3318,11 +3319,37 @@ struct types::ObjectTableKey
struct types::ObjectTableEntry
{
ReadBarriered<TypeObject> object;
ReadBarriered<Shape> shape;
Type *types;
};
static inline void
UpdateObjectTableEntryTypes(JSContext *cx, ObjectTableEntry &entry,
IdValuePair *properties, size_t nproperties)
{
for (size_t i = 0; i < nproperties; i++) {
Type type = entry.types[i];
Type ntype = GetValueTypeForTable(cx, properties[i].value);
if (ntype == type)
continue;
if (ntype.isPrimitive(JSVAL_TYPE_INT32) &&
type.isPrimitive(JSVAL_TYPE_DOUBLE))
{
/* The property types already reflect 'int32'. */
} else {
if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) &&
type.isPrimitive(JSVAL_TYPE_INT32))
{
/* Include 'double' in the property types to avoid the update below later. */
entry.types[i] = Type::DoubleType();
}
entry.object->addPropertyType(cx, IdToTypeId(properties[i].id), ntype);
}
}
}
void
TypeCompartment::fixObjectType(JSContext *cx, HandleObject obj)
TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj)
{
AutoEnterAnalysis enter(cx);
@ -3336,102 +3363,150 @@ TypeCompartment::fixObjectType(JSContext *cx, HandleObject obj)
}
/*
* Use the same type object for all singleton/JSON arrays with the same
* base shape, i.e. the same fields written in the same order. If there
* is a type mismatch with previous objects of the same shape, use the
* generic unknown type.
* Use the same type object for all singleton/JSON objects with the same
* base shape, i.e. the same fields written in the same order.
*/
JS_ASSERT(obj->isObject());
if (obj->slotSpan() == 0 || obj->inDictionaryMode() || !obj->hasEmptyElements())
return;
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(obj.get());
RootedShape baseShape(cx, obj->lastProperty());
Vector<IdValuePair> properties(cx);
if (!properties.resize(obj->slotSpan())) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
Shape *shape = obj->lastProperty();
while (!shape->isEmptyShape()) {
IdValuePair &entry = properties[shape->slot()];
entry.id = shape->propid();
entry.value = obj->getSlot(shape->slot());
shape = shape->previous();
}
ObjectTableKey::Lookup lookup(properties.begin(), properties.length(), obj->numFixedSlots());
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup);
if (p) {
/* The lookup ensures the shape matches, now check that the types match. */
Type *types = p->value.types;
for (unsigned i = 0; i < obj->slotSpan(); i++) {
Type ntype = GetValueTypeForTable(cx, obj->getSlot(i));
if (ntype != types[i]) {
if (ntype.isPrimitive(JSVAL_TYPE_INT32) &&
types[i].isPrimitive(JSVAL_TYPE_DOUBLE))
{
/* The property types already reflect 'int32'. */
} else {
if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) &&
types[i].isPrimitive(JSVAL_TYPE_INT32))
{
/* Include 'double' in the property types to avoid the walk below later. */
types[i] = Type::DoubleType();
}
Shape *shape = baseShape;
while (!shape->isEmptyShape()) {
if (shape->slot() == i) {
if (!p->value.object->unknownProperties())
p->value.object->addPropertyType(cx, IdToTypeId(shape->propid()), ntype);
break;
}
shape = shape->previous();
}
}
}
}
JS_ASSERT(obj->getProto() == p->value.object->proto);
JS_ASSERT(obj->lastProperty() == p->value.shape);
UpdateObjectTableEntryTypes(cx, p->value, properties.begin(), properties.length());
obj->setType(p->value.object);
} else {
/* Make a new type to use for the object and similar future ones. */
Rooted<TaggedProto> objProto(cx, obj->getTaggedProto());
TypeObject *objType = newTypeObject(cx, &ObjectClass, objProto);
if (!objType || !objType->addDefiniteProperties(cx, obj)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
if (obj->isIndexed())
objType->setFlags(cx, OBJECT_FLAG_SPARSE_INDEXES);
jsid *ids = cx->pod_calloc<jsid>(obj->slotSpan());
if (!ids) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
Type *types = cx->pod_calloc<Type>(obj->slotSpan());
if (!types) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
RootedShape shape(cx, baseShape);
while (!shape->isEmptyShape()) {
ids[shape->slot()] = shape->propid();
types[shape->slot()] = GetValueTypeForTable(cx, obj->getSlot(shape->slot()));
if (!objType->unknownProperties())
objType->addPropertyType(cx, IdToTypeId(shape->propid()), types[shape->slot()]);
shape = shape->previous();
}
ObjectTableKey key;
key.ids = ids;
key.nslots = obj->slotSpan();
key.nfixed = obj->numFixedSlots();
key.proto = obj->getTaggedProto();
JS_ASSERT(ObjectTableKey::match(key, obj.get()));
ObjectTableEntry entry;
entry.object = objType;
entry.types = types;
p = objectTypeTable->lookupForAdd(obj.get());
if (!objectTypeTable->add(p, key, entry)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
obj->setType(objType);
return;
}
/* Make a new type to use for the object and similar future ones. */
Rooted<TaggedProto> objProto(cx, obj->getTaggedProto());
TypeObject *objType = newTypeObject(cx, &ObjectClass, objProto);
if (!objType || !objType->addDefiniteProperties(cx, obj)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
if (obj->isIndexed())
objType->setFlags(cx, OBJECT_FLAG_SPARSE_INDEXES);
jsid *ids = cx->pod_calloc<jsid>(properties.length());
if (!ids) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
Type *types = cx->pod_calloc<Type>(properties.length());
if (!types) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
for (size_t i = 0; i < properties.length(); i++) {
ids[i] = properties[i].id;
types[i] = GetValueTypeForTable(cx, obj->getSlot(i));
if (!objType->unknownProperties())
objType->addPropertyType(cx, IdToTypeId(ids[i]), types[i]);
}
ObjectTableKey key;
key.properties = ids;
key.nproperties = properties.length();
key.nfixed = obj->numFixedSlots();
JS_ASSERT(ObjectTableKey::match(key, lookup));
ObjectTableEntry entry;
entry.object = objType;
entry.shape = obj->lastProperty();
entry.types = types;
p = objectTypeTable->lookupForAdd(lookup);
if (!objectTypeTable->add(p, key, entry)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
obj->setType(objType);
}
JSObject *
TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties)
{
AutoEnterAnalysis enter(cx);
if (!objectTypeTable) {
objectTypeTable = cx->new_<ObjectTypeTable>();
if (!objectTypeTable || !objectTypeTable->init()) {
objectTypeTable = NULL;
cx->compartment->types.setPendingNukeTypes(cx);
return NULL;
}
}
/*
* Use the object type table to allocate an object with the specified
* properties, filling in its final type and shape and failing if no cache
* entry could be found for the properties.
*/
/*
* Filter out a few cases where we don't want to use the object type table.
* Note that if the properties contain any duplicates or dense indexes,
* the lookup below will fail as such arrays of properties cannot be stored
* in the object type table --- fixObjectType populates the table with
* properties read off its input object, which cannot be duplicates, and
* ignores objects with dense indexes.
*/
if (!nproperties || nproperties >= PropertyTree::MAX_HEIGHT)
return NULL;
gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
size_t nfixed = gc::GetGCKindSlots(allocKind, &ObjectClass);
ObjectTableKey::Lookup lookup(properties, nproperties, nfixed);
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup);
if (!p)
return NULL;
RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass, allocKind));
if (!obj) {
cx->clearPendingException();
return NULL;
}
JS_ASSERT(obj->getProto() == p->value.object->proto);
RootedShape shape(cx, p->value.shape);
if (!JSObject::setLastProperty(cx, obj, shape)) {
cx->clearPendingException();
return NULL;
}
UpdateObjectTableEntryTypes(cx, p->value, properties, nproperties);
for (size_t i = 0; i < nproperties; i++)
obj->setSlot(i, properties[i].value);
obj->setType(p->value.object);
return obj;
}
/////////////////////////////////////////////////////////////////////
@ -3557,7 +3632,7 @@ TypeObject::addProperty(JSContext *cx, RawId id, Property **pprop)
}
bool
TypeObject::addDefiniteProperties(JSContext *cx, HandleObject obj)
TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj)
{
if (unknownProperties())
return true;
@ -6471,17 +6546,18 @@ TypeCompartment::sweep(FreeOp *fop)
for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) {
const ObjectTableKey &key = e.front().key;
ObjectTableEntry &entry = e.front().value;
JS_ASSERT(uintptr_t(entry.object->proto.get()) == key.proto.toWord());
bool remove = false;
if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet()))
remove = true;
for (unsigned i = 0; !remove && i < key.nslots; i++) {
if (JSID_IS_STRING(key.ids[i])) {
JSString *str = JSID_TO_STRING(key.ids[i]);
if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet()))
remove = true;
for (unsigned i = 0; !remove && i < key.nproperties; i++) {
if (JSID_IS_STRING(key.properties[i])) {
JSString *str = JSID_TO_STRING(key.properties[i]);
if (IsStringAboutToBeFinalized(&str))
remove = true;
JS_ASSERT(AtomToId((JSAtom *)str) == key.ids[i]);
JS_ASSERT(AtomToId((JSAtom *)str) == key.properties[i]);
}
JS_ASSERT(!entry.types[i].isSingleObject());
TypeObject *typeObject = NULL;
@ -6495,7 +6571,7 @@ TypeCompartment::sweep(FreeOp *fop)
}
if (remove) {
js_free(key.ids);
js_free(key.properties);
js_free(entry.types);
e.removeFront();
}
@ -6825,7 +6901,7 @@ JSCompartment::sizeOfTypeInferenceData(TypeInferenceSizes *sizes, JSMallocSizeOf
const ObjectTableEntry &value = e.front().value;
/* key.ids and values.types have the same length. */
sizes->objectTypeTables += mallocSizeOf(key.ids) + mallocSizeOf(value.types);
sizes->objectTypeTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
}
}
}

View File

@ -1054,7 +1054,7 @@ struct TypeObject : gc::Cell
/* Helpers */
bool addProperty(JSContext *cx, RawId id, Property **pprop);
bool addDefiniteProperties(JSContext *cx, HandleObject obj);
bool addDefiniteProperties(JSContext *cx, JSObject *obj);
bool matchDefiniteProperties(HandleObject obj);
void addPrototype(JSContext *cx, TypeObject *proto);
void addPropertyType(JSContext *cx, jsid id, Type type);
@ -1382,8 +1382,10 @@ struct TypeCompartment
ArrayTypeTable *arrayTypeTable;
ObjectTypeTable *objectTypeTable;
void fixArrayType(JSContext *cx, HandleObject obj);
void fixObjectType(JSContext *cx, HandleObject obj);
void fixArrayType(JSContext *cx, JSObject *obj);
void fixObjectType(JSContext *cx, JSObject *obj);
JSObject *newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties);
/* Logging fields */

View File

@ -23,6 +23,11 @@
# include <pthread_np.h>
# endif
# if defined(ANDROID)
# include <unistd.h>
# include <sys/types.h>
# endif
#else
# error "Unsupported platform"
@ -125,17 +130,43 @@ js::GetNativeStackBaseImpl()
void *stackBase = 0;
size_t stackSize = 0;
# ifdef DEBUG
int rc =
# endif
int rc;
# if defined(__OpenBSD__)
pthread_stackseg_np(pthread_self(), &ss);
rc = pthread_stackseg_np(pthread_self(), &ss);
stackBase = (void*)((size_t) ss.ss_sp - ss.ss_size);
stackSize = ss.ss_size;
# elif defined(ANDROID)
if (gettid() == getpid()) {
// bionic's pthread_attr_getstack doesn't tell the truth for the main
// thread (see bug 846670). So we scan /proc/self/maps to find the
// segment which contains the stack.
rc = -1;
FILE *fs = fopen("/proc/self/maps", "r");
if (fs) {
char line[100];
unsigned long stackAddr = (unsigned long)&sattr;
while (fgets(line, sizeof(line), fs) != NULL) {
unsigned long stackStart;
unsigned long stackEnd;
if (sscanf(line, "%lx-%lx ", &stackStart, &stackEnd) == 2 &&
stackAddr >= stackStart && stackAddr < stackEnd) {
stackBase = (void *)stackStart;
stackSize = stackEnd - stackStart;
rc = 0;
break;
}
}
fclose(fs);
}
} else
// For non main-threads pthread allocates the stack itself so it tells
// the truth.
rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize);
# else
pthread_attr_getstack(&sattr, &stackBase, &stackSize);
rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize);
# endif
JS_ASSERT(!rc);
if (rc)
MOZ_CRASH();
JS_ASSERT(stackBase);
pthread_attr_destroy(&sattr);

View File

@ -17,6 +17,40 @@ using namespace js;
using mozilla::RangedPtr;
JSONParser::~JSONParser()
{
for (size_t i = 0; i < stack.length(); i++) {
if (stack[i].state == FinishArrayElement)
js_delete(&stack[i].elements());
else
js_delete(&stack[i].properties());
}
for (size_t i = 0; i < freeElements.length(); i++)
js_delete(freeElements[i]);
for (size_t i = 0; i < freeProperties.length(); i++)
js_delete(freeProperties[i]);
}
void
JSONParser::trace(JSTracer *trc)
{
for (size_t i = 0; i < stack.length(); i++) {
if (stack[i].state == FinishArrayElement) {
ElementVector &elements = stack[i].elements();
for (size_t j = 0; j < elements.length(); j++)
gc::MarkValueRoot(trc, &elements[j], "JSONParser element");
} else {
PropertyVector &properties = stack[i].properties();
for (size_t j = 0; j < properties.length(); j++) {
gc::MarkValueRoot(trc, &properties[j].value, "JSONParser property value");
gc::MarkIdRoot(trc, &properties[j].id, "JSONParser property id");
}
}
}
}
void
JSONParser::error(const char *msg)
{
@ -483,17 +517,95 @@ JSONParser::advanceAfterProperty()
return token(Error);
}
/*
* This enum is local to JSONParser::parse, below, but ISO C++98 doesn't allow
* templates to depend on local types. Boo-urns!
*/
enum ParserState { FinishArrayElement, FinishObjectMember, JSONValue };
JSObject *
JSONParser::createFinishedObject(PropertyVector &properties)
{
/*
* Look for an existing cached type and shape for objects with this set of
* properties.
*/
if (cx->typeInferenceEnabled()) {
JSObject *obj = cx->compartment->types.newTypedObject(cx, properties.begin(),
properties.length());
if (obj)
return obj;
}
/*
* Make a new object sized for the given number of properties and fill its
* shape in manually.
*/
gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass, allocKind));
if (!obj)
return NULL;
RootedId propid(cx);
RootedValue value(cx);
for (size_t i = 0; i < properties.length(); i++) {
propid = properties[i].id;
value = properties[i].value;
if (!DefineNativeProperty(cx, obj, propid, value,
JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE,
0, 0))
{
return NULL;
}
}
/*
* Try to assign a new type to the object with type information for its
* properties, and update the initializer type object cache with this
* object's final shape.
*/
if (cx->typeInferenceEnabled())
cx->compartment->types.fixObjectType(cx, obj);
return obj;
}
inline bool
JSONParser::finishObject(MutableHandleValue vp, PropertyVector &properties)
{
JS_ASSERT(&properties == &stack.back().properties());
JSObject *obj = createFinishedObject(properties);
if (!obj)
return false;
vp.setObject(*obj);
if (!freeProperties.append(&properties))
return false;
stack.popBack();
return true;
}
inline bool
JSONParser::finishArray(MutableHandleValue vp, ElementVector &elements)
{
JS_ASSERT(&elements == &stack.back().elements());
JSObject *obj = NewDenseCopiedArray(cx, elements.length(), elements.begin());
if (!obj)
return false;
/* Try to assign a new type to the array according to its elements. */
if (cx->typeInferenceEnabled())
cx->compartment->types.fixArrayType(cx, obj);
vp.setObject(*obj);
if (!freeElements.append(&elements))
return false;
stack.popBack();
return true;
}
bool
JSONParser::parse(MutableHandleValue vp)
{
Vector<ParserState> stateStack(cx);
AutoValueVector valueStack(cx);
RootedValue value(cx);
JS_ASSERT(stack.empty());
vp.setUndefined();
@ -502,18 +614,15 @@ JSONParser::parse(MutableHandleValue vp)
while (true) {
switch (state) {
case FinishObjectMember: {
RootedValue v(cx, valueStack.popCopy());
RootedId propid(cx, AtomToId(&valueStack.popCopy().toString()->asAtom()));
RootedObject obj(cx, &valueStack.back().toObject());
if (!DefineNativeProperty(cx, obj, propid, v,
JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE,
0, 0))
{
return false;
}
PropertyVector &properties = stack.back().properties();
properties.back().value = value;
token = advanceAfterProperty();
if (token == ObjectClose)
if (token == ObjectClose) {
if (!finishObject(&value, properties))
return false;
break;
}
if (token != Comma) {
if (token == OOM)
return false;
@ -527,20 +636,22 @@ JSONParser::parse(MutableHandleValue vp)
JSONMember:
if (token == String) {
if (!valueStack.append(atomValue()))
jsid id = AtomToId(atomValue());
PropertyVector &properties = stack.back().properties();
if (!properties.append(IdValuePair(id)))
return false;
token = advancePropertyColon();
if (token != Colon) {
JS_ASSERT(token == Error);
return errorReturn();
}
if (!stateStack.append(FinishObjectMember))
return false;
goto JSONValue;
}
if (token == ObjectClose) {
JS_ASSERT(state == FinishObjectMember);
JS_ASSERT(parsingMode == LegacyJSON);
if (!finishObject(&value, stack.back().properties()))
return false;
break;
}
if (token == OOM)
@ -550,18 +661,17 @@ JSONParser::parse(MutableHandleValue vp)
return errorReturn();
case FinishArrayElement: {
Value v = valueStack.popCopy();
Rooted<JSObject*> obj(cx, &valueStack.back().toObject());
if (!js_NewbornArrayPush(cx, obj, v))
ElementVector &elements = stack.back().elements();
if (!elements.append(value.get()))
return false;
token = advanceAfterArrayElement();
if (token == Comma) {
if (!stateStack.append(FinishArrayElement))
return false;
if (token == Comma)
goto JSONValue;
}
if (token == ArrayClose)
if (token == ArrayClose) {
if (!finishArray(&value, elements))
return false;
break;
}
JS_ASSERT(token == Error);
return errorReturn();
}
@ -572,49 +682,69 @@ JSONParser::parse(MutableHandleValue vp)
JSONValueSwitch:
switch (token) {
case String:
value = stringValue();
break;
case Number:
if (!valueStack.append(token == String ? stringValue() : numberValue()))
return false;
value = numberValue();
break;
case True:
if (!valueStack.append(BooleanValue(true)))
return false;
value = BooleanValue(true);
break;
case False:
if (!valueStack.append(BooleanValue(false)))
return false;
value = BooleanValue(false);
break;
case Null:
if (!valueStack.append(NullValue()))
return false;
value = NullValue();
break;
case ArrayOpen: {
JSObject *obj = NewDenseEmptyArray(cx);
if (!obj || !valueStack.append(ObjectValue(*obj)))
ElementVector *elements;
if (!freeElements.empty()) {
elements = freeElements.popCopy();
elements->clear();
} else {
elements = cx->new_<ElementVector>(cx);
if (!elements)
return false;
}
if (!stack.append(elements))
return false;
token = advance();
if (token == ArrayClose)
if (token == ArrayClose) {
if (!finishArray(&value, *elements))
return false;
break;
if (!stateStack.append(FinishArrayElement))
return false;
}
goto JSONValueSwitch;
}
case ObjectOpen: {
JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
if (!obj || !valueStack.append(ObjectValue(*obj)))
PropertyVector *properties;
if (!freeProperties.empty()) {
properties = freeProperties.popCopy();
properties->clear();
} else {
properties = cx->new_<PropertyVector>(cx);
if (!properties)
return false;
}
if (!stack.append(properties))
return false;
token = advanceAfterObjectOpen();
if (token == ObjectClose)
if (token == ObjectClose) {
if (!finishObject(&value, *properties))
return false;
break;
}
goto JSONMember;
}
case ArrayClose:
if (parsingMode == LegacyJSON &&
!stateStack.empty() &&
stateStack.back() == FinishArrayElement) {
!stack.empty() &&
stack.back().state == FinishArrayElement) {
/*
* Previous JSON parsing accepted trailing commas in
* non-empty array syntax, and some users depend on this.
@ -624,7 +754,8 @@ JSONParser::parse(MutableHandleValue vp)
* such trailing commas only when specifically
* instructed to do so.
*/
stateStack.popBack();
if (!finishArray(&value, stack.back().elements()))
return false;
break;
}
/* FALL THROUGH */
@ -644,9 +775,9 @@ JSONParser::parse(MutableHandleValue vp)
break;
}
if (stateStack.empty())
if (stack.empty())
break;
state = stateStack.popCopy();
state = stack.back().state;
}
for (; current < end; current++) {
@ -657,7 +788,8 @@ JSONParser::parse(MutableHandleValue vp)
}
JS_ASSERT(end == current);
JS_ASSERT(valueStack.length() == 1);
vp.set(valueStack[0]);
JS_ASSERT(stack.empty());
vp.set(value);
return true;
}

View File

@ -14,10 +14,12 @@
#include "jscntxt.h"
#include "jsstr.h"
namespace js {
/*
* NB: This class must only be used on the stack as it contains a js::Value.
* NB: This class must only be used on the stack.
*/
class JSONParser
class JSONParser : private AutoGCRooter
{
public:
enum ErrorHandling { RaiseError, NoError };
@ -27,10 +29,10 @@ class JSONParser
/* Data members */
JSContext * const cx;
JS::StableCharPtr current;
const JS::StableCharPtr end;
StableCharPtr current;
const StableCharPtr end;
js::Value v;
Value v;
const ParsingMode parsingMode;
const ErrorHandling errorHandling;
@ -40,6 +42,70 @@ class JSONParser
ObjectOpen, ObjectClose,
Colon, Comma,
OOM, Error };
// State related to the parser's current position. At all points in the
// parse this keeps track of the stack of arrays and objects which have
// been started but not finished yet. The actual JS object is not
// allocated until the literal is closed, so that the result can be sized
// according to its contents and have its type and shape filled in using
// caches.
// State for an array that is currently being parsed. This includes all
// elements that have been seen so far.
typedef Vector<Value, 20> ElementVector;
// State for an object that is currently being parsed. This includes all
// the key/value pairs that have been seen so far.
typedef Vector<IdValuePair, 10> PropertyVector;
// Possible states the parser can be in between values.
enum ParserState {
// An array element has just being parsed.
FinishArrayElement,
// An object property has just been parsed.
FinishObjectMember,
// At the start of the parse, before any values have been processed.
JSONValue
};
// Stack element for an in progress array or object.
struct StackEntry {
ElementVector &elements() {
JS_ASSERT(state == FinishArrayElement);
return * static_cast<ElementVector *>(vector);
}
PropertyVector &properties() {
JS_ASSERT(state == FinishObjectMember);
return * static_cast<PropertyVector *>(vector);
}
StackEntry(ElementVector *elements)
: state(FinishArrayElement), vector(elements)
{}
StackEntry(PropertyVector *properties)
: state(FinishObjectMember), vector(properties)
{}
ParserState state;
private:
void *vector;
};
// All in progress arrays and objects being parsed, in order from outermost
// to innermost.
Vector<StackEntry, 10> stack;
// Unused element and property vectors for previous in progress arrays and
// objects. These vectors are not freed until the end of the parse to avoid
// unnecessary freeing and allocation.
Vector<ElementVector*, 5> freeElements;
Vector<PropertyVector*, 5> freeProperties;
#ifdef DEBUG
Token lastToken;
#endif
@ -58,11 +124,15 @@ class JSONParser
JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
ParsingMode parsingMode = StrictJSON,
ErrorHandling errorHandling = RaiseError)
: cx(cx),
: AutoGCRooter(cx, JSONPARSER),
cx(cx),
current(data),
end((data + length).get(), data.get(), length),
parsingMode(parsingMode),
errorHandling(errorHandling)
errorHandling(errorHandling),
stack(cx),
freeElements(cx),
freeProperties(cx)
#ifdef DEBUG
, lastToken(Error)
#endif
@ -70,6 +140,8 @@ class JSONParser
JS_ASSERT(current <= end);
}
~JSONParser();
/*
* Parse the JSON data specified at construction time. If it parses
* successfully, store the prescribed value in *vp and return true. If an
@ -95,10 +167,9 @@ class JSONParser
return v;
}
js::Value atomValue() const {
JSAtom *atomValue() const {
js::Value strval = stringValue();
JS_ASSERT(strval.toString()->isAtom());
return strval;
return &strval.toString()->asAtom();
}
Token token(Token t) {
@ -141,9 +212,18 @@ class JSONParser
void error(const char *msg);
bool errorReturn();
JSObject *createFinishedObject(PropertyVector &properties);
bool finishObject(MutableHandleValue vp, PropertyVector &properties);
bool finishArray(MutableHandleValue vp, ElementVector &elements);
friend void AutoGCRooter::trace(JSTracer *trc);
void trace(JSTracer *trc);
private:
JSONParser(const JSONParser &other) MOZ_DELETE;
void operator=(const JSONParser &other) MOZ_DELETE;
};
} /* namespace js */
#endif /* jsonparser_h___ */

View File

@ -211,6 +211,17 @@ class XDRState;
class FreeOp;
struct IdValuePair
{
jsid id;
Value value;
IdValuePair() {}
IdValuePair(jsid idArg)
: id(idArg)
{}
};
} /* namespace js */
namespace JSC {

View File

@ -811,7 +811,7 @@ class CallCompiler : public BaseCompiler
if (!linker.verifyRange(f.chunk())) {
disable();
return false;
return true;
}
linker.link(noIonCode, ic.icCall());

View File

@ -1734,7 +1734,6 @@ public:
unsigned numSubpatterns = lastSubpatternId - subpatternId + 1;
ByteDisjunction* parenthesesDisjunction = js_new<ByteDisjunction>(numSubpatterns, callFrameSize);
parenthesesDisjunction->terms.reserve(endTerm - beginTerm + 1);
parenthesesDisjunction->terms.append(ByteTerm::SubpatternBegin());
for (unsigned termInParentheses = beginTerm + 1; termInParentheses < endTerm; ++termInParentheses)
parenthesesDisjunction->terms.append(m_bodyDisjunction->terms[termInParentheses]);

View File

@ -341,7 +341,7 @@ public:
struct BytecodePattern {
WTF_MAKE_FAST_ALLOCATED;
public:
BytecodePattern(PassOwnPtr<ByteDisjunction> body, Vector<ByteDisjunction*> &allParenthesesInfo, YarrPattern& pattern, BumpPointerAllocator* allocator)
BytecodePattern(PassOwnPtr<ByteDisjunction> body, const Vector<ByteDisjunction*> &allParenthesesInfo, YarrPattern& pattern, BumpPointerAllocator* allocator)
: m_body(body)
, m_ignoreCase(pattern.m_ignoreCase)
, m_multiline(pattern.m_multiline)
@ -350,17 +350,12 @@ public:
newlineCharacterClass = pattern.newlineCharacterClass();
wordcharCharacterClass = pattern.wordcharCharacterClass();
// Trick: 'Steal' the YarrPattern's ParenthesesInfo!
// The input vector isn't used afterwards anymore,
// that way we don't have to copy the input.
JS_ASSERT(m_allParenthesesInfo.size() == 0);
m_allParenthesesInfo.swap(allParenthesesInfo);
// Trick: 'Steal' the YarrPattern's CharacterClasses!
// The input vector isn't used afterwards anymore,
// that way we don't have to copy the input.
JS_ASSERT(m_userCharacterClasses.size() == 0);
m_userCharacterClasses.swap(pattern.m_userCharacterClasses);
m_allParenthesesInfo.append(allParenthesesInfo);
m_userCharacterClasses.append(pattern.m_userCharacterClasses);
// 'Steal' the YarrPattern's CharacterClasses! We clear its
// array, so that it won't delete them on destruction. We'll
// take responsibility for that.
pattern.m_userCharacterClasses.clear();
}
~BytecodePattern()

View File

@ -491,12 +491,11 @@ public:
newDisjunction->m_parent = disjunction->m_parent;
}
PatternAlternative* newAlternative = newDisjunction->addNewAlternative();
newAlternative->m_terms.reserve(alternative->m_terms.size());
for (unsigned i = 0; i < alternative->m_terms.size(); ++i)
newAlternative->m_terms.append(copyTerm(alternative->m_terms[i], filterStartsWithBOL));
}
}
if (newDisjunction)
m_pattern.m_disjunctions.append(newDisjunction);
return newDisjunction;

View File

@ -204,10 +204,6 @@ class Vector {
for (T *p = impl.begin(); p != impl.end(); ++p)
js_delete(*p);
}
bool reserve(size_t capacity) {
return impl.reserve(capacity);
}
};
template<typename T>
@ -235,11 +231,6 @@ class Vector<OwnPtr<T> > {
delete_(*p);
return impl.clear();
}
void reserve(size_t capacity) {
// XXX yarr-oom
(void) impl.reserve(capacity);
}
};
template <typename T, size_t N>

View File

@ -5518,12 +5518,6 @@ PresShell::Paint(nsView* aViewToPaint,
aViewToPaint->GetWidget()->GetLayerManager(&isRetainingManager);
NS_ASSERTION(layerManager, "Must be in paint event");
if (!layerManager) {
// Crash so we get a good stack on how this code is getting triggered.
// See bug 847002 for details.
MOZ_CRASH();
}
// Whether or not we should set first paint when painting is
// suppressed is debatable. For now we'll do it because
// B2G relies on first paint to configure the viewport and

View File

@ -14,7 +14,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=396024
var is = window.opener.wrappedJSObject.is;
var ok = window.opener.wrappedJSObject.ok;
var todo = window.opener.wrappedJSObject.todo;
var parentFinish = window.opener.wrappedJSObject.parentFinish;
var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
var gWbp;
function printpreview() {
gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
@ -49,7 +49,7 @@ function exitprintpreview() {
}
function finish() {
parentFinish();
SimpleTest.finish();
window.close();
}

View File

@ -14,7 +14,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=482976
var is = window.opener.wrappedJSObject.is;
var ok = window.opener.wrappedJSObject.ok;
var todo = window.opener.wrappedJSObject.todo;
var parentFinish = window.opener.wrappedJSObject.parentFinish;
var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
var gWbp;
function printpreview() {
gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
@ -49,7 +49,7 @@ function exitprintpreview() {
}
function finish() {
parentFinish();
SimpleTest.finish();
window.close();
}

View File

@ -12,7 +12,7 @@ var is = window.opener.wrappedJSObject.is;
var isnot = window.opener.wrappedJSObject.isnot;
var ok = window.opener.wrappedJSObject.ok;
var todo = window.opener.wrappedJSObject.todo;
var parentFinish = window.opener.wrappedJSObject.parentFinish;
var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
var gWbp;
var ctx1;
var ctx2;
@ -66,7 +66,7 @@ function exitprintpreview() {
}
function finish() {
parentFinish();
SimpleTest.finish();
window.close();
}

View File

@ -10,28 +10,6 @@
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
if (navigator.platform.startsWith("Linux")) {
SimpleTest.expectAssertions(1); // bug 671976
} else if (navigator.platform.startsWith("Win")) {
// reliable 1 on Win7, but not on XP
SimpleTest.expectAssertions(0, 1); // bug 671976
}
function parentFinish() {
// This is called while the helper window is still open. Call
// doGC after it closes.
setTimeout(doGC, 0);
}
function doGC() {
// Garbage collecting the windows created in this test can cause
// assertions, so GC now to blame those assertions to this test.
// ("Destroying a currently-showing document", bug 671976)
SpecialPowers.gc();
setTimeout(doFinish, 0);
}
function doFinish() {
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
window.open("printpreview_helper.xul", "printpreview", "chrome,width=100,height=100");
]]></script>

Some files were not shown because too many files have changed in this diff Show More