Bug 897060 - Display select dropdowns in the parent process. r=enndeakin

Original patch by roc.
This commit is contained in:
Felipe Gomes 2013-08-25 22:34:23 -03:00
parent 9ed414bc32
commit e36a075b54
10 changed files with 278 additions and 6 deletions

View File

@ -714,3 +714,18 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
*:-moz-full-screen-ancestor #social-sidebar-box:not(:-moz-full-screen-ancestor) {
display: none;
}
/* Combobox dropdown renderer */
#ContentSelectDropdown {
max-height: 400px;
}
.contentSelectDropdown-optgroup {
font-weight: bold;
/* color: menutext used to overwrite the disabled color */
color: menutext;
}
.contentSelectDropdown-ingroup {
-moz-margin-start: 2em;
}

View File

@ -125,6 +125,9 @@
<!-- for url bar autocomplete -->
<panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
<!-- for select dropdowns -->
<menupopup id="ContentSelectDropdown" rolluponmousewheel="true" hidden="true"/>
<!-- for invalid form error message -->
<panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
<description/>
@ -1025,7 +1028,8 @@
flex="1" contenttooltip="aHTMLTooltip"
tabcontainer="tabbrowser-tabs"
contentcontextmenu="contentAreaContextMenu"
autocompletepopup="PopupAutoComplete"/>
autocompletepopup="PopupAutoComplete"
selectpopup="ContentSelectDropdown"/>
<chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
<statuspanel id="statusbar-display" inactive="true"/>
</vbox>

View File

@ -35,7 +35,7 @@
<xul:vbox flex="1" class="browserContainer">
<xul:stack flex="1" class="browserStack" anonid="browserStack">
<xul:browser anonid="initialBrowser" type="content-primary" message="true" disablehistory="true"
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectpopup"/>
</xul:stack>
</xul:vbox>
</xul:hbox>
@ -1473,6 +1473,10 @@
if (this.hasAttribute("autocompletepopup"))
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
if (this.hasAttribute("selectpopup"))
b.setAttribute("selectpopup", this.getAttribute("selectpopup"));
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
// Create the browserStack container

View File

@ -44,6 +44,7 @@
#include "mozilla/dom/HTMLOptionsCollection.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Preferences.h"
#include <algorithm>
using namespace mozilla;
@ -1816,6 +1817,14 @@ nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
} else {
// NOTE: the combo box is responsible for dropping it down
if (mComboboxFrame) {
if (XRE_GetProcessType() == GeckoProcessType_Content &&
Preferences::GetBool("browser.tabs.remote", false)) {
nsContentUtils::DispatchChromeEvent(mContent->OwnerDoc(), mContent,
NS_LITERAL_STRING("mozshowdropdown"), true,
false);
return NS_OK;
}
if (!IgnoreMouseEventForSelection(aMouseEvent)) {
return NS_OK;
}

View File

@ -48,6 +48,7 @@ toolkit.jar:
content/global/resetProfile.js (resetProfile.js)
content/global/resetProfile.xul (resetProfile.xul)
content/global/resetProfileProgress.xul (resetProfileProgress.xul)
content/global/select-child.js (select-child.js)
content/global/treeUtils.js (treeUtils.js)
content/global/viewZoomOverlay.js (viewZoomOverlay.js)
*+ content/global/bindings/autocomplete.xml (widgets/autocomplete.xml)

View File

@ -0,0 +1,13 @@
/* 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/. */
XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper",
"resource://gre/modules/SelectContentHelper.jsm");
addEventListener("mozshowdropdown", event => {
if (!event.isTrusted)
return;
new SelectContentHelper(event.target, this);
});

View File

@ -100,6 +100,13 @@
this.messageManager.addMessageListener("DOMTitleChanged", this);
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
if (this.hasAttribute("selectpopup")) {
this.messageManager.addMessageListener("Forms:ShowDropDown", this);
this.messageManager.addMessageListener("Forms:HideDropDown", this);
this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
}
this.webProgress._init();
let jsm = "resource://gre/modules/RemoteController.jsm";
@ -119,17 +126,31 @@
<method name="receiveMessage">
<parameter name="aMessage"/>
<body><![CDATA[
let json = aMessage.json;
let data = aMessage.data;
switch (aMessage.name) {
case "DOMTitleChanged":
this._contentTitle = json.title;
this._contentTitle = data.title;
break;
case "ImageDocumentLoaded":
this._imageDocument = {
width: json.width,
height: json.height
width: data.width,
height: data.height
};
break;
case "Forms:ShowDropDown": {
Cu.import("resource://gre/modules/SelectParentHelper.jsm");
let dropdown = document.getElementById(this.getAttribute("selectpopup"));
SelectParentHelper.populate(dropdown, data.options, data.selectedIndex);
SelectParentHelper.open(this, dropdown, data.rect);
break;
}
case "Forms:HideDropDown": {
Cu.import("resource://gre/modules/SelectParentHelper.jsm");
let dropdown = document.getElementById(this.getAttribute("selectpopup"));
SelectParentHelper.hide(dropdown);
}
}
]]></body>
</method>

View File

@ -0,0 +1,114 @@
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = [
"SelectContentHelper"
];
this.SelectContentHelper = function (aElement, aGlobal) {
this.element = aElement;
this.global = aGlobal;
this.init();
this.showDropDown();
}
this.SelectContentHelper.prototype = {
init: function() {
this.global.addMessageListener("Forms:SelectDropDownItem", this);
this.global.addMessageListener("Forms:DismissedDropDown", this);
this.global.addEventListener("pagehide", this);
},
uninit: function() {
this.global.removeMessageListener("Forms:SelectDropDownItem", this);
this.global.removeMessageListener("Forms:DismissedDropDown", this);
this.global.removeEventListener("pagehide", this);
this.element = null;
this.global = null;
},
showDropDown: function() {
let rect = this._getBoundingContentRect();
this.global.sendAsyncMessage("Forms:ShowDropDown", {
rect: rect,
options: this._buildOptionList(),
selectedIndex: this.element.selectedIndex,
});
},
_getBoundingContentRect: function() {
let { top, left, width, height } = this.element.getBoundingClientRect();
// We need to copy the info because the properties returned by getBoundingClientRect
// are not writable.
let rect = { left: left, top: top, width: width, height: height };
// step out of iframes and frames, offsetting scroll values
let view = this.element.ownerDocument.defaultView;
for (let frame = view; frame != this.global.content; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
let r = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
rect.left += r.left + parseInt(left, 10);
rect.top += r.top + parseInt(top, 10);
}
return rect;
},
_buildOptionList: function() {
return buildOptionListForChildren(this.element);
},
receiveMessage: function(message) {
switch (message.name) {
case "Forms:SelectDropDownItem":
this.element.selectedIndex = message.data.value;
//intentional fall-through
case "Forms:DismissedDropDown":
this.uninit();
break;
}
},
handleEvent: function(event) {
switch (event.type) {
case "pagehide":
this.global.sendAsyncMessage("Forms:HideDropDown", {});
this.uninit();
break;
}
}
}
function buildOptionListForChildren(node) {
let result = [];
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.tagName == 'OPTION' || child.tagName == 'OPTGROUP') {
let info = {
tagName: child.tagName,
textContent: child.tagName == 'OPTGROUP' ? child.getAttribute("label")
: child.textContent,
// XXX this uses a highlight color when this is the selected element.
// We need to suppress such highlighting in the content process to get
// the option's correct unhighlighted color here.
// We also need to detect default color vs. custom so that a standard
// color does not override color: menutext in the parent.
// backgroundColor: computedStyle.backgroundColor,
// color: computedStyle.color,
children: child.tagName == 'OPTGROUP' ? buildOptionListForChildren(child) : []
};
result.push(info);
}
}
return result;
}

View File

@ -0,0 +1,89 @@
"use strict";
this.EXPORTED_SYMBOLS = [
"SelectParentHelper"
];
let currentBrowser = null;
this.SelectParentHelper = {
populate: function(popup, items, selectedIndex) {
// Clear the current contents of the popup
popup.textContent = "";
populateChildren(popup, items, selectedIndex);
},
open: function(browser, popup, rect) {
currentBrowser = browser;
this._registerListeners(popup);
popup.hidden = false;
popup.openPopup(currentBrowser, "overlap", rect.left, rect.top + rect.height);
},
hide: function(popup) {
popup.hidePopup();
},
handleEvent: function(event) {
let popup = event.currentTarget;
switch (event.type) {
case "command":
if (event.target.hasAttribute("value")) {
currentBrowser.messageManager.sendAsyncMessage("Forms:SelectDropDownItem", {
value: event.target.value
});
}
popup.hidePopup();
break;
case "popuphidden":
currentBrowser.messageManager.sendAsyncMessage("Forms:DismissedDropDown", {});
currentBrowser = null;
this._unregisterListeners(popup);
break;
}
},
_registerListeners: function(popup) {
popup.addEventListener("command", this);
popup.addEventListener("popuphidden", this);
},
_unregisterListeners: function(popup) {
popup.removeEventListener("command", this);
popup.removeEventListener("popuphidden", this);
},
};
function populateChildren(element, options, selectedIndex, startIndex = 0, isGroup = false) {
let index = startIndex;
for (let option of options) {
let item = element.ownerDocument.createElement("menuitem");
item.setAttribute("label", option.textContent);
item.setAttribute("type", "radio");
if (index == selectedIndex) {
item.setAttribute("checked", "true");
}
element.appendChild(item);
if (option.children.length > 0) {
item.classList.add("contentSelectDropdown-optgroup");
item.setAttribute("disabled", "true");
index = populateChildren(element, option.children, selectedIndex, index, true);
} else {
item.setAttribute("value", index++);
if (isGroup) {
item.classList.add("contentSelectDropdown-ingroup")
}
}
}
return index;
}

View File

@ -25,6 +25,8 @@ EXTRA_JS_MODULES += [
'RemoteSecurityUI.jsm',
'RemoteWebNavigation.jsm',
'RemoteWebProgress.jsm',
'SelectContentHelper.jsm',
'SelectParentHelper.jsm',
'Sqlite.jsm',
'Task.jsm',
'TelemetryTimestamps.jsm',