Merge m-c to inbound.
@ -15,6 +15,12 @@ XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
|
||||
return devtools.require("devtools/toolkit/webconsole/utils").Utils;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
return devtools.require("devtools/server/actors/eventlooplag").EventLoopLagFront;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* The Widget Panel is an on-device developer tool that displays widgets,
|
||||
* showing visual debug information about apps. Each widget corresponds to a
|
||||
@ -358,4 +364,67 @@ let consoleWatcher = {
|
||||
return source;
|
||||
}
|
||||
};
|
||||
|
||||
devtoolsWidgetPanel.registerWatcher(consoleWatcher);
|
||||
|
||||
|
||||
let jankWatcher = {
|
||||
_client: null,
|
||||
_fronts: new Map(),
|
||||
_active: false,
|
||||
|
||||
init: function(client) {
|
||||
this._client = client;
|
||||
|
||||
SettingsListener.observe('devtools.hud.jank', false,
|
||||
this.settingsListener.bind(this));
|
||||
},
|
||||
|
||||
settingsListener: function(value) {
|
||||
if (this._active == value) {
|
||||
return;
|
||||
}
|
||||
this._active = value;
|
||||
|
||||
// Toggle the state of existing fronts.
|
||||
let fronts = this._fronts;
|
||||
for (let app of fronts.keys()) {
|
||||
if (value) {
|
||||
fronts.get(app).start();
|
||||
} else {
|
||||
fronts.get(app).stop();
|
||||
app.metrics.set('jank', 0);
|
||||
app.display();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trackApp: function(app) {
|
||||
app.metrics.set('jank', 0);
|
||||
|
||||
let front = new EventLoopLagFront(this._client, app.actor);
|
||||
this._fronts.set(app, front);
|
||||
|
||||
front.on('event-loop-lag', time => {
|
||||
app.metrics.set('jank', time);
|
||||
|
||||
if (!app.display()) {
|
||||
devtoolsWidgetPanel.log('jank: ' + time + 'ms');
|
||||
}
|
||||
});
|
||||
|
||||
if (this._active) {
|
||||
front.start();
|
||||
}
|
||||
},
|
||||
|
||||
untrackApp: function(app) {
|
||||
let fronts = this._fronts;
|
||||
if (fronts.has(app)) {
|
||||
fronts.get(app).destroy();
|
||||
fronts.delete(app);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
devtoolsWidgetPanel.registerWatcher(jankWatcher);
|
||||
|
@ -50,6 +50,9 @@
|
||||
<command id="cmd_findPrevious"
|
||||
oncommand="gFindBar.onFindAgainCommand(true);"
|
||||
observes="isImage"/>
|
||||
#ifdef XP_MACOSX
|
||||
<command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
|
||||
#endif
|
||||
<!-- work-around bug 392512 -->
|
||||
<command id="Browser:AddBookmarkAs"
|
||||
oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
|
||||
@ -350,6 +353,9 @@
|
||||
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
|
||||
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
|
||||
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
|
||||
#endif
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
|
||||
|
||||
|
@ -3037,17 +3037,7 @@
|
||||
filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
|
||||
this.mTabListeners[0] = tabListener;
|
||||
this.mTabFilters[0] = filter;
|
||||
|
||||
try {
|
||||
// We assume this can only fail because mCurrentBrowser's docShell
|
||||
// hasn't been created, yet. This may be caused by code accessing
|
||||
// gBrowser before the window has finished loading.
|
||||
this._addProgressListenerForInitialTab();
|
||||
} catch (e) {
|
||||
// The binding was constructed too early, wait until the initial
|
||||
// tab's document is ready, then add the progress listener.
|
||||
this._waitForInitialContentDocument();
|
||||
}
|
||||
this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
|
||||
|
||||
this.style.backgroundColor =
|
||||
Services.prefs.getBoolPref("browser.display.use_system_colors") ?
|
||||
@ -3083,31 +3073,6 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_addProgressListenerForInitialTab">
|
||||
<body><![CDATA[
|
||||
this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_waitForInitialContentDocument">
|
||||
<body><![CDATA[
|
||||
let obs = (subject, topic) => {
|
||||
if (this.browsers[0].contentWindow == subject) {
|
||||
Services.obs.removeObserver(obs, topic);
|
||||
this._addProgressListenerForInitialTab();
|
||||
}
|
||||
};
|
||||
|
||||
// We use content-document-global-created as an approximation for
|
||||
// "docShell is initialized". We can do this because in the
|
||||
// mTabProgressListener we care most about the STATE_STOP notification
|
||||
// that will reset mBlank. That means it's important to at least add
|
||||
// the progress listener before the initial about:blank load stops
|
||||
// if we can't do it before the load starts.
|
||||
Services.obs.addObserver(obs, "content-document-global-created", false);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
for (var i = 0; i < this.mTabListeners.length; ++i) {
|
||||
|
@ -11,6 +11,9 @@ let texts = [
|
||||
"To err is human; to forgive is not company policy."
|
||||
];
|
||||
|
||||
let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
let HasFindClipboard = Clipboard.supportsFindClipboard();
|
||||
|
||||
function addTabWithText(aText, aCallback) {
|
||||
let newTab = gBrowser.addTab("data:text/html,<h1 id='h1'>" + aText + "</h1>");
|
||||
tabs.push(newTab);
|
||||
@ -61,7 +64,9 @@ function continueTests1() {
|
||||
// Confirm the first tab is still correct, ensure re-hiding works as expected
|
||||
gBrowser.selectedTab = tabs[0];
|
||||
ok(!gFindBar.hidden, "First tab shows find bar!");
|
||||
is(gFindBar._findField.value, texts[0], "First tab persists find value!");
|
||||
// When the Find Clipboard is supported, this test not relevant.
|
||||
if (!HasFindClipboard)
|
||||
is(gFindBar._findField.value, texts[0], "First tab persists find value!");
|
||||
ok(gFindBar.getElement("highlight").checked,
|
||||
"Highlight button state persists!");
|
||||
|
||||
@ -94,7 +99,8 @@ function continueTests2() {
|
||||
ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
|
||||
is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
|
||||
gFindBar.open();
|
||||
is(gFindBar._findField.value, texts[1],
|
||||
let toTest = HasFindClipboard ? texts[2] : texts[1];
|
||||
is(gFindBar._findField.value, toTest,
|
||||
"Fourth tab has second tab's find value!");
|
||||
|
||||
newWindow = gBrowser.replaceTabWithWindow(tabs.pop());
|
||||
@ -104,7 +110,8 @@ function continueTests2() {
|
||||
// Test that findbar gets restored when a tab is moved to a new window.
|
||||
function checkNewWindow() {
|
||||
ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
|
||||
is(newWindow.gFindBar._findField.value, texts[1],
|
||||
let toTest = HasFindClipboard ? texts[2] : texts[1];
|
||||
is(newWindow.gFindBar._findField.value, toTest,
|
||||
"New window find bar has correct find value!");
|
||||
ok(!newWindow.gFindBar.getElement("find-next").disabled,
|
||||
"New window findbar has enabled buttons!");
|
||||
|
@ -2,7 +2,10 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
const {Ci: interfaces, Cc: classes} = Components;
|
||||
|
||||
let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
let HasFindClipboard = Clipboard.supportsFindClipboard();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
@ -37,7 +40,10 @@ function onFocus(win) {
|
||||
let findBar = win.gFindBar;
|
||||
selectText(win.content);
|
||||
findBar.onFindCommand();
|
||||
is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
|
||||
// When the OS supports the Find Clipboard (OSX), the find field value is
|
||||
// persisted across Fx sessions, thus not useful to test.
|
||||
if (!HasFindClipboard)
|
||||
is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
|
||||
findBar.close();
|
||||
win.close();
|
||||
finish();
|
||||
|
@ -614,6 +614,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||
<!ENTITY findAgainCmd.accesskey "g">
|
||||
<!ENTITY findAgainCmd.commandkey "g">
|
||||
<!ENTITY findAgainCmd.commandkey2 "VK_F3">
|
||||
<!ENTITY findSelectionCmd.commandkey "e">
|
||||
|
||||
<!ENTITY spellAddDictionaries.label "Add Dictionaries…">
|
||||
<!ENTITY spellAddDictionaries.accesskey "A">
|
||||
|
@ -2434,11 +2434,11 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
.panel-promo-box {
|
||||
margin: 8px -16px -16px -16px;
|
||||
padding: 8px 16px;
|
||||
background: #e5e5e5;
|
||||
border-top: 1px solid hsla(0,0%,0%,.1);
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
border-radius: 0 0 5px 5px;
|
||||
box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
|
||||
color: #808080;
|
||||
color: hsl(0,0%,30%);
|
||||
}
|
||||
|
||||
.panel-promo-icon {
|
||||
@ -3214,6 +3214,10 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
}
|
||||
}
|
||||
|
||||
#identity-popup {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
padding: 0;
|
||||
}
|
||||
@ -3223,7 +3227,8 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
}
|
||||
|
||||
#identity-popup-button-container {
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
padding: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
@ -4022,7 +4027,6 @@ toolbar[mode="icons"] > *|* > .toolbarbutton-badge[badge]:not([badge=""]):-moz-l
|
||||
}
|
||||
|
||||
#social-share-panel {
|
||||
margin-top: 3px;
|
||||
max-height: 600px;
|
||||
min-height: 100px;
|
||||
max-width: 800px;
|
||||
|
@ -4,6 +4,10 @@
|
||||
|
||||
/*** Panel and outer controls ***/
|
||||
|
||||
#downloadsPanel {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
padding: 0;
|
||||
}
|
||||
@ -19,8 +23,8 @@
|
||||
}
|
||||
|
||||
#downloadsFooter {
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
#downloadsHistory {
|
||||
@ -29,11 +33,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory {
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
}
|
||||
|
||||
#downloadsPanel[hasdownloads] > #downloadsFooter {
|
||||
background: #e5e5e5;
|
||||
border-top: 1px solid hsla(0,0%,0%,.1);
|
||||
@ -49,18 +48,13 @@
|
||||
#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
|
||||
outline: 2px -moz-mac-focusring solid;
|
||||
outline-offset: -2px;
|
||||
-moz-outline-radius-bottomleft: 5px;
|
||||
-moz-outline-radius-bottomright: 5px;
|
||||
-moz-outline-radius-bottomleft: 4px;
|
||||
-moz-outline-radius-bottomright: 4px;
|
||||
}
|
||||
|
||||
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus {
|
||||
-moz-outline-radius-topleft: 5px;
|
||||
-moz-outline-radius-topright: 5px;
|
||||
}
|
||||
|
||||
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus > .button-box {
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
-moz-outline-radius-topleft: 4px;
|
||||
-moz-outline-radius-topright: 4px;
|
||||
}
|
||||
|
||||
/*** Downloads Summary and List items ***/
|
||||
|
@ -30,11 +30,10 @@
|
||||
padding: 4px;
|
||||
background-color: hsla(0,0%,100%,.97);
|
||||
background-clip: padding-box;
|
||||
border-right: 1px solid hsla(210,4%,10%,.2);
|
||||
border-left: 1px solid hsla(210,4%,10%,.2);
|
||||
border-left: 1px solid hsla(210,4%,10%,.3);
|
||||
box-shadow: 0 3px 5px hsla(210,4%,10%,.1),
|
||||
0 0 7px hsla(210,4%,10%,.1);
|
||||
color: #222426;
|
||||
color: hsl(0,0%,15%);
|
||||
-moz-margin-start: @exitSubviewGutterWidth@;
|
||||
}
|
||||
|
||||
@ -68,19 +67,13 @@
|
||||
.panel-subview-header,
|
||||
.subviewbutton.panel-subview-footer {
|
||||
padding: 12px;
|
||||
background-color: hsla(210,4%,10%,.04);
|
||||
}
|
||||
|
||||
.panel-subview-header {
|
||||
margin: -4px -4px 4px;
|
||||
box-shadow: 0 -1px 0 hsla(210,4%,10%,.08) inset;
|
||||
color: #797c80;
|
||||
}
|
||||
|
||||
.subviewbutton.panel-subview-footer {
|
||||
margin: 4px -4px -4px;
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
|
||||
border-radius: 0;
|
||||
background-color: hsla(210,4%,10%,.05);
|
||||
box-shadow: 0 -1px 0 hsla(210,4%,10%,.05) inset;
|
||||
color: hsl(0,0%,50%);
|
||||
}
|
||||
|
||||
.cui-widget-panelview .panel-subview-header {
|
||||
@ -201,8 +194,8 @@ toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-it
|
||||
}
|
||||
|
||||
/* Help SDK buttons fit in. */
|
||||
toolbarpaletteitem[place="palette"] > toolbarbutton[sdk-button="true"] > .toolbarbutton-icon,
|
||||
toolbarbutton[sdk-button="true"][cui-areatype="menu-panel"] > .toolbarbutton-icon {
|
||||
toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
|
||||
#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
@ -320,20 +313,19 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#PanelUI-footer-inner {
|
||||
display: flex;
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
|
||||
border-top: 1px solid hsla(210,4%,10%,.14);
|
||||
}
|
||||
|
||||
#PanelUI-footer-inner > toolbarseparator {
|
||||
border: 0;
|
||||
border-left: 1px solid rgba(0,0,0,0.1);
|
||||
border-left: 1px solid hsla(210,4%,10%,.14);
|
||||
margin: 7px 0 7px;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
@ -352,15 +344,16 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
||||
-moz-appearance: none;
|
||||
box-shadow: none;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
border-bottom-style: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
transition: background-color;
|
||||
-moz-box-orient: horizontal;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status {
|
||||
border-bottom-style: solid;
|
||||
border-top: 1px solid hsla(210,4%,10%,.14);
|
||||
border-bottom: 1px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status > .toolbarbutton-text {
|
||||
@ -451,18 +444,26 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status:not([disabled]):hover,
|
||||
#PanelUI-help:not([disabled]):hover,
|
||||
#PanelUI-customize:hover,
|
||||
#PanelUI-quit:not([disabled]):hover {
|
||||
outline: 1px solid rgba(0,0,0,0.1);
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
box-shadow: none;
|
||||
outline: 1px solid hsla(210,4%,10%,.07);
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status:not([disabled]):hover {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
border-bottom-color: rgba(0,0,0,0.1);
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,0.2);
|
||||
#PanelUI-fxa-status:not([disabled]):hover:active,
|
||||
#PanelUI-help:not([disabled]):hover:active,
|
||||
#PanelUI-customize:hover:active,
|
||||
#PanelUI-quit:not([disabled]):hover:active {
|
||||
outline: 1px solid hsla(210,4%,10%,.12);
|
||||
background-color: hsla(210,4%,10%,.12);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status:not([disabled]):hover,
|
||||
#PanelUI-fxa-status:not([disabled]):hover:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#PanelUI-quit:not([disabled]):hover {
|
||||
@ -472,6 +473,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
||||
|
||||
#PanelUI-quit:not([disabled]):hover:active {
|
||||
background-color: #ad3434;
|
||||
outline-color: #992e2e;
|
||||
}
|
||||
|
||||
#customization-panelHolder #PanelUI-customize {
|
||||
@ -552,12 +554,12 @@ panelview .toolbarbutton-1@buttonStateHover@,
|
||||
#edit-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@,
|
||||
#zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
|
||||
background-color: hsla(210,4%,10%,.08);
|
||||
border-color: hsla(210,4%,10%,.1);
|
||||
border-color: hsla(210,4%,10%,.11);
|
||||
}
|
||||
|
||||
#edit-controls@inAnyPanel@@buttonStateHover@,
|
||||
#zoom-controls@inAnyPanel@@buttonStateHover@ {
|
||||
border-color: hsla(210,4%,10%,.1);
|
||||
border-color: hsla(210,4%,10%,.11);
|
||||
}
|
||||
|
||||
panelview .toolbarbutton-1@buttonStateActive@,
|
||||
@ -565,9 +567,28 @@ panelview .toolbarbutton-1@buttonStateActive@,
|
||||
.widget-overflow-list .toolbarbutton-1@buttonStateActive@,
|
||||
#edit-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@,
|
||||
#zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
|
||||
background-color: hsla(210,4%,10%,.12);
|
||||
border-color: hsla(210,4%,10%,.14);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
|
||||
}
|
||||
|
||||
.subviewbutton.panel-subview-footer {
|
||||
margin: 4px -4px -4px;
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
border-radius: 0;
|
||||
color: hsl(0,0%,25%)
|
||||
}
|
||||
|
||||
.subviewbutton.panel-subview-footer@buttonStateHover@ {
|
||||
background-color: hsla(210,4%,10%,.1);
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
}
|
||||
|
||||
.subviewbutton.panel-subview-footer@buttonStateActive@ {
|
||||
background-color: hsla(210,4%,10%,.15);
|
||||
border-color: hsla(210,4%,10%,.15);
|
||||
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
}
|
||||
|
||||
#BMB_bookmarksPopup > .subviewbutton {
|
||||
|
@ -175,12 +175,6 @@
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.variables-view-scope:focus > .title,
|
||||
.variable-or-property:focus > .title {
|
||||
background: #3689b2; /* fg-color2 */
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* CodeMirror specific styles.
|
||||
* Best effort to match the existing theme, some of the colors
|
||||
* are duplicated here to prevent weirdness in the main theme. */
|
||||
|
@ -174,12 +174,6 @@
|
||||
box-shadow: 0 0 0 1px #EFEFEF;
|
||||
}
|
||||
|
||||
.variables-view-scope:focus > .title,
|
||||
.variable-or-property:focus > .title {
|
||||
background: #4c9ed9; /* fg-color2 */
|
||||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* CodeMirror specific styles.
|
||||
* Best effort to match the existing theme, some of the colors
|
||||
* are duplicated here to prevent weirdness in the main theme. */
|
||||
|
@ -561,6 +561,18 @@
|
||||
color: #585959; /* Grey foreground text */
|
||||
}
|
||||
|
||||
.theme-dark .variables-view-scope:focus > .title,
|
||||
.theme-dark .variable-or-property:focus > .title {
|
||||
background-color: #1d4f73; /* Selection colors */
|
||||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
.theme-light .variables-view-scope:focus > .title,
|
||||
.theme-light .variable-or-property:focus > .title {
|
||||
background-color: #4c9ed9; /* Selection colors */
|
||||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
.variables-view-scope > .title {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
|
@ -26,7 +26,8 @@
|
||||
}
|
||||
|
||||
.click-to-play-plugins-notification-button-container {
|
||||
background: linear-gradient(rgba(0,0,0,0.04) 60%, transparent);
|
||||
background-color: hsla(210,4%,10%,.07);
|
||||
border-top: 1px solid hsla(210,4%,10%,.12);
|
||||
padding: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
@ -11,7 +11,9 @@
|
||||
#include "ImageContainer.h"
|
||||
#include "ImageTypes.h"
|
||||
#include "prmem.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
#include "nsIFilePicker.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
|
||||
@ -164,18 +166,38 @@ MediaEngineDefaultVideoSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile)
|
||||
#ifndef MOZ_WIDGET_ANDROID
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#else
|
||||
if (!AndroidBridge::Bridge()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
nsAutoString filePath;
|
||||
nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
|
||||
if (!filePicker)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsXPIDLString title;
|
||||
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, "Browse", title);
|
||||
int16_t mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
|
||||
|
||||
nsresult rv = filePicker->Init(nullptr, title, mode);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
filePicker->AppendFilters(nsIFilePicker::filterImages);
|
||||
|
||||
// XXX - This API should be made async
|
||||
PRInt16 dialogReturn;
|
||||
rv = filePicker->Show(&dialogReturn);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (dialogReturn == nsIFilePicker::returnCancel) {
|
||||
*aFile = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoString filePath;
|
||||
AndroidBridge::Bridge()->ShowFilePickerForMimeType(filePath, NS_LITERAL_STRING("image/*"));
|
||||
nsCOMPtr<nsIFile> localFile;
|
||||
filePicker->GetFile(getter_AddRefs(localFile));
|
||||
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsresult rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!localFile) {
|
||||
*aFile = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ADDREF(*aFile = new nsDOMFileFile(file));
|
||||
nsCOMPtr<nsIDOMFile> domFile = new nsDOMFileFile(localFile);
|
||||
domFile.forget(aFile);
|
||||
return NS_OK;
|
||||
#endif
|
||||
}
|
||||
|
@ -238,82 +238,6 @@ public class ActivityHandlerHelper implements GeckoEventListener {
|
||||
});
|
||||
}
|
||||
|
||||
private Intent getFilePickerIntent(Context context, String aMimeType) {
|
||||
ArrayList<Intent> intents = new ArrayList<Intent>();
|
||||
final Prompt.PromptListItem[] items =
|
||||
getItemsAndIntentsForFilePicker(context, aMimeType, intents);
|
||||
|
||||
if (intents.size() == 0) {
|
||||
Log.i(LOGTAG, "no activities for the file picker!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (intents.size() == 1) {
|
||||
return intents.get(0);
|
||||
}
|
||||
|
||||
final PromptService ps = GeckoAppShell.getGeckoInterface().getPromptService();
|
||||
final String title = getFilePickerTitle(context, aMimeType);
|
||||
|
||||
// Runnable has to be called to show an intent-like
|
||||
// context menu UI using the PromptService.
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override public void run() {
|
||||
ps.show(title, "", items, false, null);
|
||||
}
|
||||
});
|
||||
|
||||
String promptServiceResult = ps.getResponse(null);
|
||||
int itemId = -1;
|
||||
try {
|
||||
itemId = new JSONObject(promptServiceResult).getInt("button");
|
||||
|
||||
if (itemId == -1) {
|
||||
return null;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "result from promptservice was invalid: ", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return intents.get(itemId);
|
||||
}
|
||||
|
||||
boolean showFilePicker(Activity parentActivity, String aMimeType, ActivityResultHandler handler) {
|
||||
Intent intent = getFilePickerIntent(parentActivity, aMimeType);
|
||||
|
||||
if (intent == null) {
|
||||
return false;
|
||||
}
|
||||
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(handler));
|
||||
return true;
|
||||
}
|
||||
|
||||
String showFilePicker(Activity parentActivity, String aMimeType) {
|
||||
Intent intent = getFilePickerIntent(parentActivity, aMimeType);
|
||||
|
||||
if (intent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (intent.getAction().equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
|
||||
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraImageResultHandler));
|
||||
} else if (intent.getAction().equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
|
||||
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraVideoResultHandler));
|
||||
} else if (intent.getAction().equals(Intent.ACTION_GET_CONTENT)) {
|
||||
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mFilePickerResultHandlerSync));
|
||||
} else {
|
||||
Log.e(LOGTAG, "We should not get an intent with another action!");
|
||||
return "";
|
||||
}
|
||||
|
||||
String filePickerResult;
|
||||
while (null == (filePickerResult = mFilePickerResult.poll())) {
|
||||
GeckoAppShell.processNextNativeEvent(true);
|
||||
}
|
||||
return filePickerResult;
|
||||
}
|
||||
|
||||
/* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
|
||||
* sends the file returned to the passed in handler. If a null handler is passed in, will still
|
||||
* pick and launch the file picker, but will throw away the result.
|
||||
|
@ -31,7 +31,7 @@ import org.mozilla.gecko.prompts.Prompt;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.MenuUtils;
|
||||
|
@ -6,7 +6,7 @@ package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
@ -2,7 +2,11 @@
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -14,6 +18,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public final class EventDispatcher {
|
||||
private static final String LOGTAG = "GeckoEventDispatcher";
|
||||
private static final String GUID = "__guid__";
|
||||
|
||||
private final Map<String, CopyOnWriteArrayList<GeckoEventListener>> mEventListeners
|
||||
= new HashMap<String, CopyOnWriteArrayList<GeckoEventListener>>();
|
||||
@ -52,18 +57,16 @@ public final class EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public String dispatchEvent(String message) {
|
||||
public void dispatchEvent(String message) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(message);
|
||||
return dispatchEvent(json);
|
||||
dispatchEvent(json);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "dispatchEvent: malformed JSON.", e);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public String dispatchEvent(JSONObject json) {
|
||||
public void dispatchEvent(JSONObject json) {
|
||||
// {
|
||||
// "type": "value",
|
||||
// "event_specific": "value",
|
||||
@ -87,29 +90,29 @@ public final class EventDispatcher {
|
||||
|
||||
if (listeners == null || listeners.size() == 0) {
|
||||
Log.d(LOGTAG, "dispatchEvent: no listeners registered for event '" + type + "'");
|
||||
return "";
|
||||
return;
|
||||
}
|
||||
|
||||
String response = null;
|
||||
|
||||
for (GeckoEventListener listener : listeners) {
|
||||
listener.handleMessage(type, json);
|
||||
if (listener instanceof GeckoEventResponder) {
|
||||
String newResponse = ((GeckoEventResponder)listener).getResponse(json);
|
||||
if (response != null && newResponse != null) {
|
||||
Log.e(LOGTAG, "Received two responses for message of type " + type);
|
||||
}
|
||||
response = newResponse;
|
||||
}
|
||||
}
|
||||
|
||||
if (response != null)
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void sendResponse(JSONObject message, JSONObject response) {
|
||||
try {
|
||||
response.put(GUID, message.getString(GUID));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Return", response.toString()));
|
||||
} catch(Exception ex) { }
|
||||
}
|
||||
|
||||
public static void sendError(JSONObject message, JSONObject error) {
|
||||
try {
|
||||
error.put(GUID, message.getString(GUID));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Error", error.toString()));
|
||||
} catch(Exception ex) { }
|
||||
}
|
||||
}
|
@ -23,9 +23,7 @@ import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.updater.UpdateService;
|
||||
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||
import org.mozilla.gecko.util.ActivityResultHandler;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
@ -134,7 +132,6 @@ public abstract class GeckoApp
|
||||
ContextGetter,
|
||||
GeckoAppShell.GeckoInterface,
|
||||
GeckoEventListener,
|
||||
GeckoEventResponder,
|
||||
GeckoMenu.Callback,
|
||||
GeckoMenu.MenuPresenter,
|
||||
LocationListener,
|
||||
@ -187,7 +184,6 @@ public abstract class GeckoApp
|
||||
protected GeckoProfile mProfile;
|
||||
public static int mOrientation;
|
||||
protected boolean mIsRestoringActivity;
|
||||
private String mCurrentResponse = "";
|
||||
|
||||
private ContactService mContactService;
|
||||
private PromptService mPromptService;
|
||||
@ -685,7 +681,7 @@ public abstract class GeckoApp
|
||||
}
|
||||
JSONObject handlersJSON = new JSONObject();
|
||||
handlersJSON.put("apps", new JSONArray(appList));
|
||||
mCurrentResponse = handlersJSON.toString();
|
||||
EventDispatcher.sendResponse(message, handlersJSON);
|
||||
} else if (event.equals("Intent:Open")) {
|
||||
GeckoAppShell.openUriExternal(message.optString("url"),
|
||||
message.optString("mime"), message.optString("packageName"),
|
||||
@ -693,7 +689,9 @@ public abstract class GeckoApp
|
||||
} else if (event.equals("Locale:Set")) {
|
||||
setLocale(message.getString("locale"));
|
||||
} else if (event.equals("NativeApp:IsDebuggable")) {
|
||||
mCurrentResponse = getIsDebuggable() ? "true" : "false";
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("isDebuggable", getIsDebuggable() ? "true" : "false");
|
||||
EventDispatcher.sendResponse(message, ret);
|
||||
} else if (event.equals("SystemUI:Visibility")) {
|
||||
setSystemUiVisible(message.getBoolean("visible"));
|
||||
}
|
||||
@ -702,12 +700,6 @@ public abstract class GeckoApp
|
||||
}
|
||||
}
|
||||
|
||||
public String getResponse(JSONObject origMessage) {
|
||||
String res = mCurrentResponse;
|
||||
mCurrentResponse = "";
|
||||
return res;
|
||||
}
|
||||
|
||||
void onStatePurged() { }
|
||||
|
||||
/**
|
||||
|
@ -18,7 +18,6 @@ import org.mozilla.gecko.mozglue.JNITarget;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.prompts.PromptService;
|
||||
import org.mozilla.gecko.util.ActivityResultHandler;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.ProxySelector;
|
||||
@ -1427,20 +1426,6 @@ public class GeckoAppShell
|
||||
getGeckoInterface().setFullScreen(fullscreen);
|
||||
}
|
||||
|
||||
@WrapElementForJNI(stubName = "ShowFilePickerForExtensionsWrapper")
|
||||
public static String showFilePickerForExtensions(String aExtensions) {
|
||||
if (getGeckoInterface() != null)
|
||||
return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), getMimeTypeFromExtensions(aExtensions));
|
||||
return "";
|
||||
}
|
||||
|
||||
@WrapElementForJNI(stubName = "ShowFilePickerForMimeTypeWrapper")
|
||||
public static String showFilePickerForMimeType(String aMimeType) {
|
||||
if (getGeckoInterface() != null)
|
||||
return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), aMimeType);
|
||||
return "";
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void performHapticFeedback(boolean aIsLongPress) {
|
||||
// Don't perform haptic feedback if a vibration is currently playing,
|
||||
@ -2345,8 +2330,8 @@ public class GeckoAppShell
|
||||
}
|
||||
|
||||
@WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
|
||||
public static String handleGeckoMessage(String message) {
|
||||
return sEventDispatcher.dispatchEvent(message);
|
||||
public static void handleGeckoMessage(String message) {
|
||||
sEventDispatcher.dispatchEvent(message);
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
@ -2630,17 +2615,6 @@ public class GeckoAppShell
|
||||
return true;
|
||||
}
|
||||
|
||||
static native void notifyFilePickerResult(String filePath, long id);
|
||||
|
||||
@WrapElementForJNI(stubName = "ShowFilePickerAsyncWrapper")
|
||||
public static void showFilePickerAsync(String aMimeType, final long id) {
|
||||
sActivityHelper.showFilePickerAsync(getGeckoInterface().getActivity(), aMimeType, new ActivityHandlerHelper.FileResultHandler() {
|
||||
public void gotFile(String filename) {
|
||||
GeckoAppShell.notifyFilePickerResult(filename, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void notifyWakeLockChanged(String topic, String state) {
|
||||
if (getGeckoInterface() != null)
|
||||
|
@ -5,9 +5,7 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -140,7 +138,7 @@ class JavaAddonManager implements GeckoEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private static class CallbackWrapper implements GeckoEventResponder {
|
||||
private static class CallbackWrapper implements GeckoEventListener {
|
||||
private final Handler.Callback mDelegate;
|
||||
private Bundle mBundle;
|
||||
|
||||
@ -184,16 +182,14 @@ class JavaAddonManager implements GeckoEventListener {
|
||||
Message msg = new Message();
|
||||
msg.setData(mBundle);
|
||||
mDelegate.handleMessage(msg);
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("response", mBundle.getString("response"));
|
||||
EventDispatcher.sendResponse(json, obj);
|
||||
mBundle = null;
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Caught exception thrown from wrapped addon message handler", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponse(JSONObject origMessage) {
|
||||
String response = mBundle.getString("response");
|
||||
mBundle = null;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -24,14 +24,12 @@ import java.util.HashMap;
|
||||
* Helper class to get, set, and observe Android Shared Preferences.
|
||||
*/
|
||||
public final class SharedPreferencesHelper
|
||||
implements GeckoEventResponder
|
||||
implements GeckoEventListener
|
||||
{
|
||||
public static final String LOGTAG = "GeckoAndSharedPrefs";
|
||||
|
||||
protected final Context mContext;
|
||||
|
||||
protected String mResponse;
|
||||
|
||||
// mListeners is not synchronized because it is only updated in
|
||||
// handleObserve, which is called from Gecko serially.
|
||||
protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
|
||||
@ -117,7 +115,7 @@ public final class SharedPreferencesHelper
|
||||
* must include a String name, and a String type in ["bool", "int",
|
||||
* "string"].
|
||||
*/
|
||||
private String handleGet(JSONObject message) throws JSONException {
|
||||
private JSONArray handleGet(JSONObject message) throws JSONException {
|
||||
if (!message.has("branch")) {
|
||||
Log.e(LOGTAG, "No branch specified for SharedPreference:Get; aborting.");
|
||||
return null;
|
||||
@ -156,7 +154,7 @@ public final class SharedPreferencesHelper
|
||||
jsonValues.put(jsonValue);
|
||||
}
|
||||
|
||||
return jsonValues.toString();
|
||||
return jsonValues;
|
||||
}
|
||||
|
||||
private static class ChangeListener
|
||||
@ -225,8 +223,6 @@ public final class SharedPreferencesHelper
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
// Everything here is synchronous and serial, so we need not worry about
|
||||
// overwriting an in-progress response.
|
||||
mResponse = null;
|
||||
|
||||
try {
|
||||
if (event.equals("SharedPreferences:Set")) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
@ -237,9 +233,9 @@ public final class SharedPreferencesHelper
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
|
||||
}
|
||||
// Synchronous and serial, so we are the only consumer of
|
||||
// mResponse and can write to it freely.
|
||||
mResponse = handleGet(message);
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("values", handleGet(message));
|
||||
EventDispatcher.sendResponse(message, obj);
|
||||
} else if (event.equals("SharedPreferences:Observe")) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
|
||||
@ -254,9 +250,4 @@ public final class SharedPreferencesHelper
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponse(JSONObject origMessage) {
|
||||
return mResponse;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuItem;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -101,6 +101,8 @@ public class BrowserContract {
|
||||
public static final class Favicons implements CommonColumns, DateSyncColumns {
|
||||
private Favicons() {}
|
||||
|
||||
public static final String TABLE_NAME = "favicons";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "favicons");
|
||||
|
||||
public static final String URL = "url";
|
||||
@ -112,6 +114,8 @@ public class BrowserContract {
|
||||
public static final class Thumbnails implements CommonColumns {
|
||||
private Thumbnails() {}
|
||||
|
||||
public static final String TABLE_NAME = "thumbnails";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "thumbnails");
|
||||
|
||||
public static final String URL = "url";
|
||||
@ -122,6 +126,10 @@ public class BrowserContract {
|
||||
public static final class Bookmarks implements CommonColumns, URLColumns, FaviconColumns, SyncColumns {
|
||||
private Bookmarks() {}
|
||||
|
||||
public static final String TABLE_NAME = "bookmarks";
|
||||
|
||||
public static final String VIEW_WITH_FAVICONS = "bookmarks_with_favicons";
|
||||
|
||||
public static final int FIXED_ROOT_ID = 0;
|
||||
public static final int FAKE_DESKTOP_FOLDER_ID = -1;
|
||||
public static final int FIXED_READING_LIST_ID = -2;
|
||||
@ -173,6 +181,11 @@ public class BrowserContract {
|
||||
@RobocopTarget
|
||||
public static final class History implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns, SyncColumns {
|
||||
private History() {}
|
||||
|
||||
public static final String TABLE_NAME = "history";
|
||||
|
||||
public static final String VIEW_WITH_FAVICONS = "history_with_favicons";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
|
||||
public static final Uri CONTENT_OLD_URI = Uri.withAppendedPath(AUTHORITY_URI, "history/old");
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
|
||||
@ -183,6 +196,11 @@ public class BrowserContract {
|
||||
@RobocopTarget
|
||||
public static final class Combined implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns {
|
||||
private Combined() {}
|
||||
|
||||
public static final String VIEW_NAME = "combined";
|
||||
|
||||
public static final String VIEW_WITH_FAVICONS = "combined_with_favicons";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
|
||||
|
||||
public static final int DISPLAY_NORMAL = 0;
|
||||
@ -316,4 +334,47 @@ public class BrowserContract {
|
||||
public static final String IMAGE_URL = "image_url";
|
||||
public static final String CREATED = "created";
|
||||
}
|
||||
|
||||
/*
|
||||
* Contains names and schema definitions for tables and views
|
||||
* no longer being used by current ContentProviders. These values are used
|
||||
* to make incremental updates to the schema during a database upgrade. Will be
|
||||
* removed with bug 947018.
|
||||
*/
|
||||
static final class Obsolete {
|
||||
public static final String TABLE_IMAGES = "images";
|
||||
public static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
|
||||
public static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
|
||||
public static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images";
|
||||
|
||||
public static final class Images implements CommonColumns, SyncColumns {
|
||||
private Images() {}
|
||||
|
||||
public static final String URL = "url_key";
|
||||
public static final String FAVICON_URL = "favicon_url";
|
||||
public static final String FAVICON = "favicon";
|
||||
public static final String THUMBNAIL = "thumbnail";
|
||||
public static final String _ID = "_id";
|
||||
public static final String GUID = "guid";
|
||||
public static final String DATE_CREATED = "created";
|
||||
public static final String DATE_MODIFIED = "modified";
|
||||
public static final String IS_DELETED = "deleted";
|
||||
}
|
||||
|
||||
public static final class Combined {
|
||||
private Combined() {}
|
||||
|
||||
public static final String THUMBNAIL = "thumbnail";
|
||||
}
|
||||
|
||||
static final String TABLE_BOOKMARKS_JOIN_IMAGES = Bookmarks.TABLE_NAME + " LEFT OUTER JOIN " +
|
||||
Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
|
||||
DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
|
||||
|
||||
static final String TABLE_HISTORY_JOIN_IMAGES = History.TABLE_NAME + " LEFT OUTER JOIN " +
|
||||
Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, History.URL) + " = " +
|
||||
DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
|
||||
|
||||
static final String FAVICON_DB = "favicon_urls.db";
|
||||
}
|
||||
}
|
||||
|
1748
mobile/android/base/db/BrowserDatabaseHelper.java
Normal file
@ -81,4 +81,19 @@ public class DBUtils {
|
||||
Log.d(LOGTAG, "Failed to unlock database");
|
||||
GeckoAppShell.listOfOpenFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that 0-byte arrays aren't added as favicon or thumbnail data.
|
||||
* @param values ContentValues of query
|
||||
* @param columnName Name of data column to verify
|
||||
*/
|
||||
public static void stripEmptyByteArray(ContentValues values, String columnName) {
|
||||
if (values.containsKey(columnName)) {
|
||||
byte[] data = values.getAsByteArray(columnName);
|
||||
if (data == null || data.length == 0) {
|
||||
Log.w(LOGTAG, "Tried to insert an empty or non-byte-array image. Ignoring.");
|
||||
values.putNull(columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
270
mobile/android/base/db/TransactionalProvider.java
Normal file
@ -0,0 +1,270 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
* Abstract class containing methods needed to make a SQLite-based content provider with a
|
||||
* database helper of type T. Abstract methods insertInTransaction, deleteInTransaction and
|
||||
* updateInTransaction all called within a DB transaction so failed modifications can be rolled-back.
|
||||
*/
|
||||
public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends ContentProvider {
|
||||
private static final String LOGTAG = "GeckoTransProvider";
|
||||
protected Context mContext;
|
||||
protected PerProfileDatabases<T> mDatabases;
|
||||
|
||||
/*
|
||||
* Returns the name of the database file. Used to get a path
|
||||
* to the DB file.
|
||||
*
|
||||
* @return name of the database file
|
||||
*/
|
||||
abstract protected String getDatabaseName();
|
||||
|
||||
/*
|
||||
* Creates and returns an instance of a DB helper. Given a
|
||||
* context and a path to the DB file
|
||||
*
|
||||
* @param context to use to create the database helper
|
||||
* @param databasePath path to the DB file
|
||||
* @return instance of the database helper
|
||||
*/
|
||||
abstract protected T createDatabaseHelper(Context context, String databasePath);
|
||||
|
||||
/*
|
||||
* Inserts an item into the database within a DB transaction.
|
||||
*
|
||||
* @param uri query URI
|
||||
* @param values column values to be inserted
|
||||
* @return a URI for the newly inserted item
|
||||
*/
|
||||
abstract protected Uri insertInTransaction(Uri uri, ContentValues values);
|
||||
|
||||
/*
|
||||
* Deletes items from the database within a DB transaction.
|
||||
*
|
||||
* @param uri query URI
|
||||
* @param selection An optional filter to match rows to update.
|
||||
* @param selectionArgs arguments for the selection
|
||||
* @return number of rows impacted by the deletion
|
||||
*/
|
||||
abstract protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
|
||||
|
||||
/*
|
||||
* Updates the database within a DB transaction.
|
||||
*
|
||||
* @param uri Query URI
|
||||
* @param values A set of column_name/value pairs to add to the database.
|
||||
* @param selection An optional filter to match rows to update.
|
||||
* @param selectionArgs Arguments for the selection
|
||||
* @return number of rows impacted by the update
|
||||
*/
|
||||
abstract protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
|
||||
|
||||
/*
|
||||
* Fetches a readable database based on the profile indicated in the
|
||||
* passed URI. If the URI does not contain a profile param, the default profile
|
||||
* is used.
|
||||
*
|
||||
* @param uri content URI optionally indicating the profile of the user
|
||||
* @return instance of a readable SQLiteDatabase
|
||||
*/
|
||||
protected SQLiteDatabase getReadableDatabase(Uri uri) {
|
||||
String profile = null;
|
||||
if (uri != null) {
|
||||
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
||||
}
|
||||
|
||||
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetches a writeable database based on the profile indicated in the
|
||||
* passed URI. If the URI does not contain a profile param, the default profile
|
||||
* is used
|
||||
*
|
||||
* @param uri content URI optionally indicating the profile of the user
|
||||
* @return instance of a writeable SQLiteDatabase
|
||||
*/
|
||||
protected SQLiteDatabase getWritableDatabase(Uri uri) {
|
||||
String profile = null;
|
||||
if (uri != null) {
|
||||
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
||||
}
|
||||
|
||||
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
synchronized (this) {
|
||||
mContext = getContext();
|
||||
mDatabases = new PerProfileDatabases<T>(
|
||||
getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
|
||||
@Override
|
||||
public T makeDatabaseHelper(Context context, String databasePath) {
|
||||
return createDatabaseHelper(context, databasePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
trace("Calling delete on URI: " + uri);
|
||||
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
int deleted = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
trace("Beginning delete transaction: " + uri);
|
||||
db.beginTransaction();
|
||||
try {
|
||||
deleted = deleteInTransaction(uri, selection, selectionArgs);
|
||||
db.setTransactionSuccessful();
|
||||
trace("Successful delete transaction: " + uri);
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
} else {
|
||||
deleted = deleteInTransaction(uri, selection, selectionArgs);
|
||||
}
|
||||
|
||||
if (deleted > 0) {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
trace("Calling insert on URI: " + uri);
|
||||
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
Uri result = null;
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
trace("Beginning insert transaction: " + uri);
|
||||
db.beginTransaction();
|
||||
try {
|
||||
result = insertInTransaction(uri, values);
|
||||
db.setTransactionSuccessful();
|
||||
trace("Successful insert transaction: " + uri);
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
} else {
|
||||
result = insertInTransaction(uri, values);
|
||||
}
|
||||
} catch (SQLException sqle) {
|
||||
Log.e(LOGTAG, "exception in DB operation", sqle);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
Log.e(LOGTAG, "don't know how to perform that insert", uoe);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection,
|
||||
String[] selectionArgs) {
|
||||
trace("Calling update on URI: " + uri);
|
||||
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
int updated = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
trace("Beginning update transaction: " + uri);
|
||||
db.beginTransaction();
|
||||
try {
|
||||
updated = updateInTransaction(uri, values, selection, selectionArgs);
|
||||
db.setTransactionSuccessful();
|
||||
trace("Successful update transaction: " + uri);
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
} else {
|
||||
updated = updateInTransaction(uri, values, selection, selectionArgs);
|
||||
}
|
||||
|
||||
if (updated > 0) {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
||||
if (values == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int numValues = values.length;
|
||||
int successes = 0;
|
||||
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
insertInTransaction(uri, values[i]);
|
||||
successes++;
|
||||
}
|
||||
trace("Flushing DB bulkinsert...");
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
if (successes > 0) {
|
||||
mContext.getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return successes;
|
||||
}
|
||||
|
||||
protected boolean isTest(Uri uri) {
|
||||
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
|
||||
return !TextUtils.isEmpty(isTest);
|
||||
}
|
||||
|
||||
// Calculate these once, at initialization. isLoggable is too expensive to
|
||||
// have in-line in each log call.
|
||||
private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
|
||||
private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
|
||||
protected static void trace(String message) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, message);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void debug(String message) {
|
||||
if (logDebug) {
|
||||
Log.d(LOGTAG, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.ZoomConstraints;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -11,7 +11,7 @@ import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.ZoomConstraints;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
@ -16,7 +16,7 @@ import org.mozilla.gecko.TouchEventInterceptor;
|
||||
import org.mozilla.gecko.ZoomConstraints;
|
||||
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
@ -8,7 +8,7 @@ package org.mozilla.gecko.gfx;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoThread;
|
||||
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
@ -6,7 +6,7 @@
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -7,7 +7,7 @@ package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
@ -26,7 +26,7 @@ import org.mozilla.gecko.background.healthreport.HealthReportStorage.Measurement
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
|
||||
import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
|
||||
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
@ -95,7 +95,6 @@ public class BrowserSearch extends HomeFragment
|
||||
private HomeListView mList;
|
||||
|
||||
// Client that performs search suggestion queries
|
||||
@RobocopTarget
|
||||
private volatile SuggestClient mSuggestClient;
|
||||
|
||||
// List of search engines from gecko
|
||||
@ -519,8 +518,8 @@ public class BrowserSearch extends HomeFragment
|
||||
// set yet. e.g. Robocop tests might set it directly before search
|
||||
// engines are loaded.
|
||||
if (mSuggestClient == null && !isPrivate) {
|
||||
mSuggestClient = new SuggestClient(getActivity(), suggestTemplate,
|
||||
SUGGESTION_TIMEOUT, SUGGESTION_MAX);
|
||||
setSuggestClient(new SuggestClient(getActivity(), suggestTemplate,
|
||||
SUGGESTION_TIMEOUT, SUGGESTION_MAX));
|
||||
}
|
||||
} else {
|
||||
searchEngines.add(engine);
|
||||
@ -545,6 +544,20 @@ public class BrowserSearch extends HomeFragment
|
||||
filterSuggestions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the private SuggestClient instance. Should only be called if the suggestClient is
|
||||
* null (i.e. has not yet been initialized or has been nulled). Non-private access is
|
||||
* for testing purposes only.
|
||||
*/
|
||||
@RobocopTarget
|
||||
public void setSuggestClient(final SuggestClient client) {
|
||||
if (mSuggestClient != null) {
|
||||
throw new IllegalStateException("Can only set the SuggestClient if it has not " +
|
||||
"yet been initialized!");
|
||||
}
|
||||
mSuggestClient = client;
|
||||
}
|
||||
|
||||
private void showSuggestionsOptIn() {
|
||||
// Return if the ViewStub was already inflated - an inflated ViewStub is removed from the
|
||||
// View hierarchy so a second call to findViewById will return null.
|
||||
|
@ -26,7 +26,7 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* Use network-based search suggestions.
|
||||
*/
|
||||
class SuggestClient {
|
||||
public class SuggestClient {
|
||||
private static final String LOGTAG = "GeckoSuggestClient";
|
||||
private static final String USER_AGENT = GeckoAppShell.getGeckoInterface().getDefaultUAString();
|
||||
|
||||
@ -39,7 +39,7 @@ class SuggestClient {
|
||||
// the maximum number of suggestions to return
|
||||
private final int mMaxResults;
|
||||
|
||||
// used by robocop for testing; referenced via reflection
|
||||
// used by robocop for testing
|
||||
private boolean mCheckNetwork;
|
||||
|
||||
// used to make suggestions appear instantly after opt-in
|
||||
|
@ -41,12 +41,10 @@ gujar.sources += [
|
||||
'util/ActivityResultHandler.java',
|
||||
'util/ActivityResultHandlerMap.java',
|
||||
'util/Clipboard.java',
|
||||
'util/EventDispatcher.java',
|
||||
'util/FloatUtils.java',
|
||||
'util/GamepadUtils.java',
|
||||
'util/GeckoBackgroundThread.java',
|
||||
'util/GeckoEventListener.java',
|
||||
'util/GeckoEventResponder.java',
|
||||
'util/GeckoJarReader.java',
|
||||
'util/HardwareUtils.java',
|
||||
'util/INIParser.java',
|
||||
@ -112,6 +110,7 @@ gbjar.sources += [
|
||||
'CustomEditText.java',
|
||||
'DataReportingNotification.java',
|
||||
'db/BrowserContract.java',
|
||||
'db/BrowserDatabaseHelper.java',
|
||||
'db/BrowserDB.java',
|
||||
'db/BrowserProvider.java',
|
||||
'db/DBUtils.java',
|
||||
@ -122,9 +121,11 @@ gbjar.sources += [
|
||||
'db/PerProfileDatabases.java',
|
||||
'db/SQLiteBridgeContentProvider.java',
|
||||
'db/TabsProvider.java',
|
||||
'db/TransactionalProvider.java',
|
||||
'Distribution.java',
|
||||
'DoorHangerPopup.java',
|
||||
'EditBookmarkDialog.java',
|
||||
'EventDispatcher.java',
|
||||
'favicons/cache/FaviconCache.java',
|
||||
'favicons/cache/FaviconCacheElement.java',
|
||||
'favicons/cache/FaviconsForURL.java',
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
package org.mozilla.gecko.prompts;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.DateTimePicker;
|
||||
@ -54,7 +53,6 @@ import android.widget.TimePicker;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener {
|
||||
@ -66,7 +64,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
private AlertDialog mDialog;
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private ConcurrentLinkedQueue<String> mPromptQueue;
|
||||
private final Context mContext;
|
||||
private PromptCallback mCallback;
|
||||
private String mGuid;
|
||||
@ -80,16 +77,9 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
private static int mInputPaddingSize;
|
||||
private static int mMinRowSize;
|
||||
|
||||
public Prompt(Context context, ConcurrentLinkedQueue<String> queue) {
|
||||
this(context);
|
||||
mCallback = null;
|
||||
mPromptQueue = queue;
|
||||
}
|
||||
|
||||
public Prompt(Context context, PromptCallback callback) {
|
||||
this(context);
|
||||
mCallback = callback;
|
||||
mPromptQueue = null;
|
||||
}
|
||||
|
||||
private Prompt(Context context) {
|
||||
@ -415,10 +405,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
aReturn.put("guid", mGuid);
|
||||
} catch(JSONException ex) { }
|
||||
|
||||
if (mPromptQueue != null) {
|
||||
mPromptQueue.offer(aReturn.toString());
|
||||
}
|
||||
|
||||
// poke the Gecko thread in case it's waiting for new events
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createNoOpEvent());
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
package org.mozilla.gecko.prompts;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.AllCapsTextView;
|
||||
import org.mozilla.gecko.widget.DateTimePicker;
|
||||
|
@ -5,27 +5,28 @@
|
||||
|
||||
package org.mozilla.gecko.prompts;
|
||||
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class PromptService implements GeckoEventResponder {
|
||||
public class PromptService implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoPromptService";
|
||||
|
||||
private final ConcurrentLinkedQueue<String> mPromptQueue;
|
||||
private final Context mContext;
|
||||
|
||||
public PromptService(Context context) {
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:Show", this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:ShowTop", this);
|
||||
mPromptQueue = new ConcurrentLinkedQueue<String>();
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@ -41,11 +42,7 @@ public class PromptService implements GeckoEventResponder {
|
||||
@Override
|
||||
public void run() {
|
||||
Prompt p;
|
||||
if (callback != null) {
|
||||
p = new Prompt(mContext, callback);
|
||||
} else {
|
||||
p = new Prompt(mContext, mPromptQueue);
|
||||
}
|
||||
p = new Prompt(mContext, callback);
|
||||
p.show(aTitle, aText, aMenuList, aMultipleSelection);
|
||||
}
|
||||
});
|
||||
@ -58,35 +55,18 @@ public class PromptService implements GeckoEventResponder {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean isAsync = message.optBoolean("async");
|
||||
Prompt p;
|
||||
if (isAsync) {
|
||||
p = new Prompt(mContext, new Prompt.PromptCallback() {
|
||||
public void onPromptFinished(String jsonResult) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", jsonResult));
|
||||
p = new Prompt(mContext, new Prompt.PromptCallback() {
|
||||
public void onPromptFinished(String jsonResult) {
|
||||
try {
|
||||
EventDispatcher.sendResponse(message, new JSONObject(jsonResult));
|
||||
} catch(JSONException ex) {
|
||||
Log.i(LOGTAG, "Error building json response", ex);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
p = new Prompt(mContext, mPromptQueue);
|
||||
}
|
||||
}
|
||||
});
|
||||
p.show(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// GeckoEventResponder implementation
|
||||
@Override
|
||||
public String getResponse(final JSONObject origMessage) {
|
||||
if (origMessage.optBoolean("async")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// we only handle one kind of message in handleMessage, and this is the
|
||||
// response we provide for that message
|
||||
String result;
|
||||
while (null == (result = mPromptQueue.poll())) {
|
||||
GeckoAppShell.processNextNativeEvent(true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
|
||||
private String mBaseIpUrl;
|
||||
|
||||
protected AboutHomeComponent mAboutHome;
|
||||
protected AppMenuComponent mAppMenu;
|
||||
protected ToolbarComponent mToolbar;
|
||||
|
||||
static {
|
||||
@ -120,6 +121,7 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
|
||||
|
||||
private void initComponents() {
|
||||
mAboutHome = new AboutHomeComponent(this);
|
||||
mAppMenu = new AppMenuComponent(this);
|
||||
mToolbar = new ToolbarComponent(this);
|
||||
}
|
||||
|
||||
@ -163,6 +165,9 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
|
||||
case ABOUTHOME:
|
||||
return mAboutHome;
|
||||
|
||||
case APPMENU:
|
||||
return mAppMenu;
|
||||
|
||||
case TOOLBAR:
|
||||
return mToolbar;
|
||||
|
||||
|
@ -21,6 +21,7 @@ public interface UITestContext {
|
||||
|
||||
public static enum ComponentType {
|
||||
ABOUTHOME,
|
||||
APPMENU,
|
||||
TOOLBAR
|
||||
}
|
||||
|
||||
|
143
mobile/android/base/tests/components/AppMenuComponent.java
Normal file
@ -0,0 +1,143 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.tests.components;
|
||||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
|
||||
|
||||
import org.mozilla.gecko.menu.MenuItemActionBar;
|
||||
import org.mozilla.gecko.menu.MenuItemDefault;
|
||||
import org.mozilla.gecko.tests.helpers.*;
|
||||
import org.mozilla.gecko.tests.UITestContext;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import com.jayway.android.robotium.solo.Condition;
|
||||
import com.jayway.android.robotium.solo.RobotiumUtils;
|
||||
import com.jayway.android.robotium.solo.Solo;
|
||||
|
||||
import android.view.View;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class representing any interactions that take place on the app menu.
|
||||
*/
|
||||
public class AppMenuComponent extends BaseComponent {
|
||||
public enum MenuItem {
|
||||
FORWARD(R.string.forward),
|
||||
NEW_TAB(R.string.new_tab);
|
||||
|
||||
private final int resourceID;
|
||||
private String stringResource;
|
||||
|
||||
MenuItem(final int resourceID) {
|
||||
this.resourceID = resourceID;
|
||||
}
|
||||
|
||||
public String getString(final Solo solo) {
|
||||
if (stringResource == null) {
|
||||
stringResource = solo.getString(resourceID);
|
||||
}
|
||||
|
||||
return stringResource;
|
||||
}
|
||||
};
|
||||
|
||||
public AppMenuComponent(final UITestContext testContext) {
|
||||
super(testContext);
|
||||
}
|
||||
|
||||
private void assertMenuIsNotOpen() {
|
||||
assertFalse("Menu is not open", isMenuOpen());
|
||||
}
|
||||
|
||||
private View getOverflowMenuButtonView() {
|
||||
return mSolo.getView(R.id.menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a MenuItemActionBar/MenuItemDefault with the given text set as contentDescription / text.
|
||||
*
|
||||
* Will return null when the Android legacy menu is in use.
|
||||
*
|
||||
* This method is dependent on not having two views with equivalent contentDescription / text.
|
||||
*/
|
||||
private View findAppMenuItemView(String text) {
|
||||
final List<View> views = mSolo.getViews();
|
||||
|
||||
final List<MenuItemActionBar> menuItemActionBarList = RobotiumUtils.filterViews(MenuItemActionBar.class, views);
|
||||
for (MenuItemActionBar menuItem : menuItemActionBarList) {
|
||||
if (menuItem.getContentDescription().equals(text)) {
|
||||
return menuItem;
|
||||
}
|
||||
}
|
||||
|
||||
final List<MenuItemDefault> menuItemDefaultList = RobotiumUtils.filterViews(MenuItemDefault.class, views);
|
||||
for (MenuItemDefault menuItem : menuItemDefaultList) {
|
||||
if (menuItem.getText().equals(text)) {
|
||||
return menuItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void pressMenuItem(MenuItem menuItem) {
|
||||
openAppMenu();
|
||||
|
||||
final String text = menuItem.getString(mSolo);
|
||||
final View menuItemView = findAppMenuItemView(text);
|
||||
|
||||
if (menuItemView != null) {
|
||||
assertTrue("The menu item is enabled", menuItemView.isEnabled());
|
||||
assertEquals("The menu item is visible", View.VISIBLE, menuItemView.getVisibility());
|
||||
|
||||
mSolo.clickOnView(menuItemView);
|
||||
} else {
|
||||
// We could not find a view representing this menu item: Let's let Robotium try to
|
||||
// locate and click it in the legacy Android menu (devices with Android 2.x).
|
||||
//
|
||||
// Even though we already opened the menu to see if we can locate the menu item,
|
||||
// Robotium will also try to open the menu if it doesn't find an open dialog (Does
|
||||
// not happen in this case).
|
||||
mSolo.clickOnMenuItem(text, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void openAppMenu() {
|
||||
assertMenuIsNotOpen();
|
||||
|
||||
if (HardwareUtils.hasMenuButton()) {
|
||||
mSolo.sendKey(Solo.MENU);
|
||||
} else {
|
||||
pressOverflowMenuButton();
|
||||
}
|
||||
|
||||
waitForMenuOpen();
|
||||
}
|
||||
|
||||
private void pressOverflowMenuButton() {
|
||||
final View overflowMenuButton = getOverflowMenuButtonView();
|
||||
|
||||
assertTrue("The overflow menu button is enabled", overflowMenuButton.isEnabled());
|
||||
assertEquals("The overflow menu button is visible", View.VISIBLE, overflowMenuButton.getVisibility());
|
||||
|
||||
mSolo.clickOnView(overflowMenuButton, true);
|
||||
}
|
||||
|
||||
private boolean isMenuOpen() {
|
||||
// The presence of the "New tab" menu item is our best guess about whether
|
||||
// the menu is open or not.
|
||||
return mSolo.searchText(MenuItem.NEW_TAB.getString(mSolo));
|
||||
}
|
||||
|
||||
private void waitForMenuOpen() {
|
||||
WaitHelper.waitFor("menu to open", new Condition() {
|
||||
@Override
|
||||
public boolean isSatisfied() {
|
||||
return isMenuOpen();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ package org.mozilla.gecko.tests.helpers;
|
||||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
|
||||
|
||||
import org.mozilla.gecko.tests.components.AppMenuComponent;
|
||||
import org.mozilla.gecko.tests.components.ToolbarComponent;
|
||||
import org.mozilla.gecko.tests.UITestContext;
|
||||
import org.mozilla.gecko.tests.UITestContext.ComponentType;
|
||||
@ -22,12 +23,14 @@ final public class NavigationHelper {
|
||||
private static UITestContext sContext;
|
||||
private static Solo sSolo;
|
||||
|
||||
private static AppMenuComponent sAppMenu;
|
||||
private static ToolbarComponent sToolbar;
|
||||
|
||||
protected static void init(final UITestContext context) {
|
||||
sContext = context;
|
||||
sSolo = context.getSolo();
|
||||
|
||||
sAppMenu = (AppMenuComponent) context.getComponent(ComponentType.APPMENU);
|
||||
sToolbar = (ToolbarComponent) context.getComponent(ComponentType.TOOLBAR);
|
||||
}
|
||||
|
||||
@ -81,8 +84,7 @@ final public class NavigationHelper {
|
||||
WaitHelper.waitForPageLoad(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Press forward with APPMENU component
|
||||
throw new UnsupportedOperationException("Not yet implemented.");
|
||||
sAppMenu.pressMenuItem(AppMenuComponent.MenuItem.FORWARD);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -4,13 +4,10 @@
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
Components.utils.import("resource://gre/modules/Messaging.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/Home.jsm");
|
||||
|
||||
function sendMessageToJava(msg) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
const TEXT = "The quick brown fox jumps over the lazy dog.";
|
||||
|
||||
function start() {
|
||||
|
@ -1,15 +1,15 @@
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
import org.mozilla.gecko.*;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.SuggestClient;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.RuntimeException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -98,34 +98,20 @@ public class testSearchSuggestions extends BaseTest {
|
||||
}
|
||||
|
||||
private void connectSuggestClient(final Activity activity) {
|
||||
try {
|
||||
// create a SuggestClient that uses robocop_suggestions.sjs
|
||||
ClassLoader classLoader = getActivity().getApplicationContext().getClassLoader();
|
||||
Class suggestClass = classLoader.loadClass("org.mozilla.gecko.home.SuggestClient");
|
||||
Constructor suggestConstructor = suggestClass.getConstructor(
|
||||
new Class[] { Context.class, String.class, int.class });
|
||||
String suggestTemplate = getAbsoluteRawUrl(SUGGESTION_TEMPLATE);
|
||||
waitForTest(new BooleanTest() {
|
||||
@Override
|
||||
public boolean test() {
|
||||
final Fragment browserSearch = getBrowserSearch();
|
||||
return (browserSearch != null);
|
||||
}
|
||||
}, SUGGESTION_TIMEOUT);
|
||||
|
||||
Object client = suggestConstructor.newInstance(activity, suggestTemplate, SUGGESTION_TIMEOUT);
|
||||
final BrowserSearch browserSearch = (BrowserSearch) getBrowserSearch();
|
||||
|
||||
// replace mSuggestClient with test client
|
||||
final Class browserSearchClass = classLoader.loadClass("org.mozilla.gecko.home.BrowserSearch");
|
||||
final Field suggestClientField = browserSearchClass.getDeclaredField("mSuggestClient");
|
||||
suggestClientField.setAccessible(true);
|
||||
|
||||
waitForTest(new BooleanTest() {
|
||||
@Override
|
||||
public boolean test() {
|
||||
final Fragment browserSearch = getBrowserSearch();
|
||||
return (browserSearch != null);
|
||||
}
|
||||
}, SUGGESTION_TIMEOUT);
|
||||
|
||||
final Fragment browserSearch = getBrowserSearch();
|
||||
suggestClientField.set(browserSearch, client);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error setting SuggestClient", e);
|
||||
}
|
||||
final String suggestTemplate = getAbsoluteRawUrl(SUGGESTION_TEMPLATE);
|
||||
final SuggestClient client = new SuggestClient(activity, suggestTemplate,
|
||||
SUGGESTION_TIMEOUT);
|
||||
browserSearch.setSuggestClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,11 +26,11 @@ public class testSessionHistory extends UITest {
|
||||
NavigationHelper.goBack();
|
||||
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
|
||||
|
||||
// TODO: Implement this functionality and uncomment.
|
||||
/*
|
||||
NavigationHelper.goForward();
|
||||
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
|
||||
|
||||
// TODO: Implement this functionality and uncomment.
|
||||
/*
|
||||
NavigationHelper.reload();
|
||||
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
|
||||
*/
|
||||
|
@ -1,16 +0,0 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
*
|
||||
* 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/.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public interface GeckoEventResponder extends GeckoEventListener {
|
||||
String getResponse(JSONObject response);
|
||||
}
|
@ -11,9 +11,8 @@ import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.ActivityResultHandler;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.WebappAllocator;
|
||||
|
||||
@ -38,14 +37,13 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class EventListener implements GeckoEventListener, GeckoEventResponder {
|
||||
public class EventListener implements GeckoEventListener {
|
||||
|
||||
private static final String LOGTAG = "GeckoWebappEventListener";
|
||||
|
||||
private EventListener() { }
|
||||
|
||||
private static EventListener mEventListener;
|
||||
private String mCurrentResponse = "";
|
||||
|
||||
private static EventListener getEventListener() {
|
||||
if (mEventListener == null) {
|
||||
@ -110,23 +108,20 @@ public class EventListener implements GeckoEventListener, GeckoEventResponder {
|
||||
String manifestURL = message.getString("manifestURL");
|
||||
String origin = message.getString("origin");
|
||||
|
||||
// preInstallWebapp will return a File object pointing to the profile directory of the webapp
|
||||
mCurrentResponse = preInstallWebapp(name, manifestURL, origin).toString();
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("profile", preInstallWebapp(name, manifestURL, origin).toString());
|
||||
EventDispatcher.sendResponse(message, obj);
|
||||
} else if (event.equals("WebApps:GetApkVersions")) {
|
||||
mCurrentResponse = getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
|
||||
message.getJSONArray("packageNames")).toString();
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("versions", getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
|
||||
message.getJSONArray("packageNames")).toString());
|
||||
EventDispatcher.sendResponse(message, obj);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getResponse(JSONObject origMessage) {
|
||||
String res = mCurrentResponse;
|
||||
mCurrentResponse = "";
|
||||
return res;
|
||||
}
|
||||
|
||||
// Not used by MOZ_ANDROID_SYNTHAPKS.
|
||||
public static File preInstallWebapp(String aTitle, String aURI, String aOrigin) {
|
||||
int index = WebappAllocator.getInstance(GeckoAppShell.getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);
|
||||
|
@ -9,16 +9,13 @@ let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
document.addEventListener("DOMContentLoaded", init, false);
|
||||
|
||||
function dump(a) {
|
||||
Services.console.logStringMessage(a);
|
||||
}
|
||||
|
||||
function sendMessageToJava(aMessage) {
|
||||
Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
|
||||
}
|
||||
|
||||
function init() {
|
||||
let anchors = document.querySelectorAll(".maybe-later");
|
||||
for(let anchor of anchors) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
||||
|
||||
// Name of Android SharedPreference controlling whether to upload
|
||||
@ -24,10 +25,6 @@ const WRAPPER_VERSION = 1;
|
||||
const EVENT_HEALTH_REQUEST = "HealthReport:Request";
|
||||
const EVENT_HEALTH_RESPONSE = "HealthReport:Response";
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// about:healthreport prefs are stored in Firefox's default Android
|
||||
// SharedPreferences.
|
||||
let sharedPrefs = new SharedPreferences();
|
||||
|
@ -27,6 +27,9 @@ Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
|
||||
"resource://gre/modules/Messaging.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
@ -169,10 +172,6 @@ function dump(a) {
|
||||
Services.console.logStringMessage(a);
|
||||
}
|
||||
|
||||
function sendMessageToJava(aMessage) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
|
||||
}
|
||||
|
||||
function doChangeMaxLineBoxWidth(aWidth) {
|
||||
gReflowPending = null;
|
||||
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
|
||||
@ -7138,18 +7137,20 @@ var WebappsUI = {
|
||||
doInstall: function doInstall(aData) {
|
||||
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
|
||||
let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
|
||||
let showPrompt = true;
|
||||
|
||||
if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
|
||||
if (Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
|
||||
// Get a profile for the app to be installed in. We'll download everything before creating the icons.
|
||||
let origin = aData.app.origin;
|
||||
let profilePath = sendMessageToJava({
|
||||
type: "Webapps:Preinstall",
|
||||
name: manifest.name,
|
||||
manifestURL: aData.app.manifestURL,
|
||||
origin: origin
|
||||
});
|
||||
if (profilePath) {
|
||||
sendMessageToJava({
|
||||
type: "Webapps:Preinstall",
|
||||
name: manifest.name,
|
||||
manifestURL: aData.app.manifestURL,
|
||||
origin: origin
|
||||
}, (data) => {
|
||||
let profilePath = JSON.parse(data).profile;
|
||||
if (!profilePath)
|
||||
return;
|
||||
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(profilePath);
|
||||
|
||||
@ -8032,12 +8033,12 @@ var ExternalApps = {
|
||||
},
|
||||
|
||||
updatePageAction: function updatePageAction(uri) {
|
||||
let apps = HelperApps.getAppsForUri(uri);
|
||||
|
||||
if (apps.length > 0)
|
||||
this._setUriForPageAction(uri, apps);
|
||||
else
|
||||
this._removePageAction();
|
||||
HelperApps.getAppsForUri(uri, { filterHttp: true }, (apps) => {
|
||||
if (apps.length > 0)
|
||||
this._setUriForPageAction(uri, apps);
|
||||
else
|
||||
this._removePageAction();
|
||||
});
|
||||
},
|
||||
|
||||
_setUriForPageAction: function setUriForPageAction(uri, apps) {
|
||||
|
@ -8,10 +8,7 @@ const Cc = Components.classes;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function sendMessageToJava(aMessage) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
|
||||
}
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
function ContentDispatchChooser() {}
|
||||
|
||||
|
@ -65,11 +65,6 @@ PaymentUI.prototype = {
|
||||
return this.bundle = Services.strings.createBundle("chrome://browser/locale/payments.properties");
|
||||
},
|
||||
|
||||
sendMessageToJava: function(aMsg) {
|
||||
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
|
||||
return JSON.parse(data);
|
||||
},
|
||||
|
||||
confirmPaymentRequest: function confirmPaymentRequest(aRequestId,
|
||||
aRequests,
|
||||
aSuccessCb,
|
||||
|
@ -805,11 +805,6 @@ let PromptUtils = {
|
||||
return hostname;
|
||||
},
|
||||
|
||||
sendMessageToJava: function(aMsg) {
|
||||
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
|
||||
return JSON.parse(data);
|
||||
},
|
||||
|
||||
fireDialogEvent: function(aDomWin, aEventName) {
|
||||
// accessing the document object can throw if this window no longer exists. See bug 789888.
|
||||
try {
|
||||
|
@ -17,9 +17,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", "resource://gre/modules/Messaging.jsm");
|
||||
|
||||
function dump(a) {
|
||||
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
|
||||
Services.console.logStringMessage(a);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@ -63,11 +64,6 @@ SessionStore.prototype = {
|
||||
OS.File.remove(this._sessionFileBackup.path);
|
||||
},
|
||||
|
||||
_sendMessageToJava: function (aMsg) {
|
||||
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
|
||||
return JSON.parse(data);
|
||||
},
|
||||
|
||||
observe: function ss_observe(aSubject, aTopic, aData) {
|
||||
let self = this;
|
||||
let observerService = Services.obs;
|
||||
@ -132,7 +128,7 @@ SessionStore.prototype = {
|
||||
}
|
||||
|
||||
// Let Java know we're done restoring tabs so tabs added after this can be animated
|
||||
this._sendMessageToJava({
|
||||
sendMessageToJava({
|
||||
type: "Session:RestoreEnd"
|
||||
});
|
||||
}.bind(this)
|
||||
@ -415,7 +411,7 @@ SessionStore.prototype = {
|
||||
|
||||
// If we have private data, send it to Java; otherwise, send null to
|
||||
// indicate that there is no private data
|
||||
this._sendMessageToJava({
|
||||
sendMessageToJava({
|
||||
type: "PrivateBrowsing:Data",
|
||||
session: (privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Prompt.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
|
||||
let ContentAreaUtils = {};
|
||||
@ -35,7 +36,19 @@ App.prototype = {
|
||||
var HelperApps = {
|
||||
get defaultHttpHandlers() {
|
||||
delete this.defaultHttpHandlers;
|
||||
return this.defaultHttpHandlers = this.getAppsForProtocol("http");
|
||||
this.defaultHttpHandlers = this.getAppsForProtocol("http");
|
||||
return this.defaultHttpHandlers;
|
||||
},
|
||||
|
||||
get defaultHtmlHandlers() {
|
||||
delete this.defaultHtmlHandlers;
|
||||
let handlers = this.getAppsForUri(Services.io.newURI("http://www.example.com/index.html", null, null));
|
||||
|
||||
this.defaultHtmlHandlers = {};
|
||||
handlers.forEach(function(app) {
|
||||
this.defaultHtmlHandlers[app.name] = app;
|
||||
}, this);
|
||||
return this.defaultHtmlHandlers;
|
||||
},
|
||||
|
||||
get protoSvc() {
|
||||
@ -70,29 +83,54 @@ var HelperApps = {
|
||||
return results;
|
||||
},
|
||||
|
||||
getAppsForUri: function getAppsForUri(uri, flags = { filterHttp: true }) {
|
||||
getAppsForUri: function getAppsForUri(uri, flags = { filterHttp: true, filterHtml: true }, callback) {
|
||||
flags.filterHttp = "filterHttp" in flags ? flags.filterHttp : true;
|
||||
flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true;
|
||||
|
||||
// Query for apps that can/can't handle the mimetype
|
||||
let msg = this._getMessage("Intent:GetHandlers", uri, flags);
|
||||
let data = this._sendMessage(msg);
|
||||
if (!data)
|
||||
return [];
|
||||
let parseData = (d) => {
|
||||
let apps = []
|
||||
|
||||
let apps = this._parseApps(data.apps);
|
||||
if (!d)
|
||||
return apps;
|
||||
|
||||
if (flags.filterHttp) {
|
||||
apps = apps.filter(function(app) {
|
||||
return app.name && !this.defaultHttpHandlers[app.name];
|
||||
}, this);
|
||||
apps = this._parseApps(d.apps);
|
||||
|
||||
if (flags.filterHttp) {
|
||||
apps = apps.filter(function(app) {
|
||||
return app.name && !this.defaultHttpHandlers[app.name];
|
||||
}, this);
|
||||
}
|
||||
|
||||
if (flags.filterHtml) {
|
||||
// Matches from the first '.' to the end of the string, '?', or '#'
|
||||
let ext = /\.([^\?#]*)/.exec(uri.path);
|
||||
if (ext && (ext[1] === "html" || ext[1] === "htm")) {
|
||||
apps = apps.filter(function(app) {
|
||||
return app.name && !this.defaultHtmlHandlers[app.name];
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
if (!callback) {
|
||||
let data = this._sendMessageSync(msg);
|
||||
if (!data)
|
||||
return [];
|
||||
return parseData(JSON.parse(data));
|
||||
} else {
|
||||
sendMessageToJava(msg, function(data) {
|
||||
callback(parseData(JSON.parse(data)));
|
||||
});
|
||||
}
|
||||
|
||||
return apps;
|
||||
},
|
||||
|
||||
launchUri: function launchUri(uri) {
|
||||
let msg = this._getMessage("Intent:Open", uri);
|
||||
this._sendMessage(msg);
|
||||
sendMessageToJava(msg);
|
||||
},
|
||||
|
||||
_parseApps: function _parseApps(appInfo) {
|
||||
@ -132,11 +170,19 @@ var HelperApps = {
|
||||
className: app.activityName
|
||||
});
|
||||
|
||||
this._sendMessage(msg);
|
||||
sendMessageToJava(msg);
|
||||
},
|
||||
|
||||
_sendMessage: function(msg) {
|
||||
let res = Services.androidBridge.handleGeckoMessage(JSON.stringify(msg));
|
||||
return JSON.parse(res);
|
||||
_sendMessageSync: function(msg) {
|
||||
let res = null;
|
||||
sendMessageToJava(msg, function(data) {
|
||||
res = data;
|
||||
});
|
||||
|
||||
let thread = Services.tm.currentThread;
|
||||
while (res == null)
|
||||
thread.processNextEvent(true);
|
||||
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
// See bug 915424
|
||||
function resolveGeckoURI(aURI) {
|
||||
@ -27,10 +28,6 @@ function resolveGeckoURI(aURI) {
|
||||
return aURI;
|
||||
}
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
|
||||
}
|
||||
|
||||
function BannerMessage(options) {
|
||||
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
this.id = uuidgen.generateUUID().toString();
|
||||
@ -48,32 +45,21 @@ function BannerMessage(options) {
|
||||
this.onclick = options.onclick;
|
||||
}
|
||||
|
||||
let HomeBanner = Object.freeze({
|
||||
let HomeBanner = (function () {
|
||||
// Holds the messages that will rotate through the banner.
|
||||
_messages: {},
|
||||
let _messages = {};
|
||||
|
||||
// A queue used to keep track of which message to show next.
|
||||
_queue: [],
|
||||
let _queue = [];
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case "HomeBanner:Get":
|
||||
this._handleGet();
|
||||
break;
|
||||
|
||||
case "HomeBanner:Click":
|
||||
this._handleClick(data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_handleGet: function() {
|
||||
let _handleGet = function() {
|
||||
// Get the message from the front of the queue, then add it back
|
||||
// to the end of the queue to show it again later.
|
||||
let id = this._queue.shift();
|
||||
this._queue.push(id);
|
||||
let id = _queue.shift();
|
||||
_queue.push(id);
|
||||
|
||||
let message = this._messages[id];
|
||||
let message = _messages[id];
|
||||
sendMessageToJava({
|
||||
type: "HomeBanner:Data",
|
||||
id: message.id,
|
||||
@ -83,59 +69,73 @@ let HomeBanner = Object.freeze({
|
||||
|
||||
if (message.onshown)
|
||||
message.onshown();
|
||||
},
|
||||
};
|
||||
|
||||
_handleClick: function(id) {
|
||||
let message = this._messages[id];
|
||||
let _handleClick = function(id) {
|
||||
let message = _messages[id];
|
||||
if (message.onclick)
|
||||
message.onclick();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new banner message to the rotation.
|
||||
*
|
||||
* @return id Unique identifer for the message.
|
||||
*/
|
||||
add: function(options) {
|
||||
let message = new BannerMessage(options);
|
||||
this._messages[message.id] = message;
|
||||
return Object.freeze({
|
||||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case "HomeBanner:Get":
|
||||
_handleGet();
|
||||
break;
|
||||
|
||||
// Add the new message to the end of the queue.
|
||||
this._queue.push(message.id);
|
||||
case "HomeBanner:Click":
|
||||
_handleClick(data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// If this is the first message we're adding, add
|
||||
// observers to listen for requests from the Java UI.
|
||||
if (Object.keys(this._messages).length == 1) {
|
||||
Services.obs.addObserver(this, "HomeBanner:Get", false);
|
||||
Services.obs.addObserver(this, "HomeBanner:Click", false);
|
||||
/**
|
||||
* Adds a new banner message to the rotation.
|
||||
*
|
||||
* @return id Unique identifer for the message.
|
||||
*/
|
||||
add: function(options) {
|
||||
let message = new BannerMessage(options);
|
||||
_messages[message.id] = message;
|
||||
|
||||
// Send a message to Java, in case there's an active HomeBanner
|
||||
// waiting for a response.
|
||||
this._handleGet();
|
||||
// Add the new message to the end of the queue.
|
||||
_queue.push(message.id);
|
||||
|
||||
// If this is the first message we're adding, add
|
||||
// observers to listen for requests from the Java UI.
|
||||
if (Object.keys(_messages).length == 1) {
|
||||
Services.obs.addObserver(this, "HomeBanner:Get", false);
|
||||
Services.obs.addObserver(this, "HomeBanner:Click", false);
|
||||
|
||||
// Send a message to Java, in case there's an active HomeBanner
|
||||
// waiting for a response.
|
||||
_handleGet();
|
||||
}
|
||||
|
||||
return message.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a banner message from the rotation.
|
||||
*
|
||||
* @param id The id of the message to remove.
|
||||
*/
|
||||
remove: function(id) {
|
||||
delete _messages[id];
|
||||
|
||||
// Remove the message from the queue.
|
||||
let index = _queue.indexOf(id);
|
||||
_queue.splice(index, 1);
|
||||
|
||||
// If there are no more messages, remove the observers.
|
||||
if (Object.keys(_messages).length == 0) {
|
||||
Services.obs.removeObserver(this, "HomeBanner:Get");
|
||||
Services.obs.removeObserver(this, "HomeBanner:Click");
|
||||
}
|
||||
}
|
||||
|
||||
return message.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a banner message from the rotation.
|
||||
*
|
||||
* @param id The id of the message to remove.
|
||||
*/
|
||||
remove: function(id) {
|
||||
delete this._messages[id];
|
||||
|
||||
// Remove the message from the queue.
|
||||
let index = this._queue.indexOf(id);
|
||||
this._queue.splice(index, 1);
|
||||
|
||||
// If there are no more messages, remove the observers.
|
||||
if (Object.keys(this._messages).length == 0) {
|
||||
Services.obs.removeObserver(this, "HomeBanner:Get");
|
||||
Services.obs.removeObserver(this, "HomeBanner:Click");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
function Panel(options) {
|
||||
if ("id" in options)
|
||||
@ -151,53 +151,30 @@ function Panel(options) {
|
||||
this.views = options.views;
|
||||
}
|
||||
|
||||
let HomePanels = Object.freeze({
|
||||
// Valid layouts for a panel.
|
||||
Layout: Object.freeze({
|
||||
FRAME: "frame"
|
||||
}),
|
||||
|
||||
// Valid types of views for a dataset.
|
||||
View: Object.freeze({
|
||||
LIST: "list",
|
||||
GRID: "grid"
|
||||
}),
|
||||
|
||||
// Valid actions for a panel.
|
||||
Action: Object.freeze({
|
||||
INSTALL: "install",
|
||||
REFRESH: "refresh"
|
||||
}),
|
||||
|
||||
// Valid item handlers for a panel view.
|
||||
ItemHandler: Object.freeze({
|
||||
BROWSER: "browser",
|
||||
INTENT: "intent"
|
||||
}),
|
||||
|
||||
let HomePanels = (function () {
|
||||
// Holds the currrent set of registered panels.
|
||||
_panels: {},
|
||||
let _panels = {};
|
||||
|
||||
_panelToJSON : function(panel) {
|
||||
let _panelToJSON = function(panel) {
|
||||
return {
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
layout: panel.layout,
|
||||
views: panel.views
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
_handleGet: function(data) {
|
||||
let _handleGet = function(data) {
|
||||
let requestId = data.requestId;
|
||||
let ids = data.ids || null;
|
||||
|
||||
let panels = [];
|
||||
for (let id in this._panels) {
|
||||
let panel = this._panels[id];
|
||||
for (let id in _panels) {
|
||||
let panel = _panels[id];
|
||||
|
||||
// Null ids means we want to fetch all available panels
|
||||
if (ids == null || ids.indexOf(panel.id) >= 0) {
|
||||
panels.push(this._panelToJSON(panel));
|
||||
panels.push(_panelToJSON(panel));
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,92 +183,117 @@ let HomePanels = Object.freeze({
|
||||
panels: panels,
|
||||
requestId: requestId
|
||||
});
|
||||
},
|
||||
|
||||
add: function(options) {
|
||||
let panel = new Panel(options);
|
||||
if (!panel.id || !panel.title) {
|
||||
throw "Home.panels: Can't create a home panel without an id and title!";
|
||||
}
|
||||
|
||||
let action = options.action;
|
||||
|
||||
// Bail if the panel already exists, except when we're refreshing
|
||||
// an existing panel instance.
|
||||
if (panel.id in this._panels && action != this.Action.REFRESH) {
|
||||
throw "Home.panels: Panel already exists: id = " + panel.id;
|
||||
}
|
||||
|
||||
if (!this._valueExists(this.Layout, panel.layout)) {
|
||||
throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
|
||||
}
|
||||
|
||||
for (let view of panel.views) {
|
||||
if (!this._valueExists(this.View, view.type)) {
|
||||
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
|
||||
}
|
||||
|
||||
if (!view.itemHandler) {
|
||||
// Use BROWSER item handler by default
|
||||
view.itemHandler = this.ItemHandler.BROWSER;
|
||||
} else if (!this._valueExists(this.ItemHandler, view.itemHandler)) {
|
||||
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
|
||||
}
|
||||
|
||||
if (!view.dataset) {
|
||||
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
|
||||
}
|
||||
}
|
||||
|
||||
this._panels[panel.id] = panel;
|
||||
|
||||
if (action) {
|
||||
let messageType;
|
||||
|
||||
switch(action) {
|
||||
case this.Action.INSTALL:
|
||||
messageType = "HomePanels:Install";
|
||||
break;
|
||||
|
||||
case this.Action.REFRESH:
|
||||
messageType = "HomePanels:Refresh";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
type: messageType,
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
if (!(id in this._panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
|
||||
let panel = this._panels[id];
|
||||
delete this._panels[id];
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Helper function used to see if a value is in an object.
|
||||
_valueExists: function(obj, value) {
|
||||
let _valueExists = function(obj, value) {
|
||||
for (let key in obj) {
|
||||
if (obj[key] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return Object.freeze({
|
||||
// Valid layouts for a panel.
|
||||
Layout: Object.freeze({
|
||||
FRAME: "frame"
|
||||
}),
|
||||
|
||||
// Valid types of views for a dataset.
|
||||
View: Object.freeze({
|
||||
LIST: "list",
|
||||
GRID: "grid"
|
||||
}),
|
||||
|
||||
// Valid actions for a panel.
|
||||
Action: Object.freeze({
|
||||
INSTALL: "install",
|
||||
REFRESH: "refresh"
|
||||
}),
|
||||
|
||||
// Valid item handlers for a panel view.
|
||||
ItemHandler: Object.freeze({
|
||||
BROWSER: "browser",
|
||||
INTENT: "intent"
|
||||
}),
|
||||
|
||||
add: function(options) {
|
||||
let panel = new Panel(options);
|
||||
if (!panel.id || !panel.title) {
|
||||
throw "Home.panels: Can't create a home panel without an id and title!";
|
||||
}
|
||||
|
||||
let action = options.action;
|
||||
|
||||
// Bail if the panel already exists, except when we're refreshing
|
||||
// an existing panel instance.
|
||||
if (panel.id in _panels && action != this.Action.REFRESH) {
|
||||
throw "Home.panels: Panel already exists: id = " + panel.id;
|
||||
}
|
||||
|
||||
if (!_valueExists(this.Layout, panel.layout)) {
|
||||
throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
|
||||
}
|
||||
|
||||
for (let view of panel.views) {
|
||||
if (!_valueExists(this.View, view.type)) {
|
||||
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
|
||||
}
|
||||
|
||||
if (!view.itemHandler) {
|
||||
// Use BROWSER item handler by default
|
||||
view.itemHandler = this.ItemHandler.BROWSER;
|
||||
} else if (!_valueExists(this.ItemHandler, view.itemHandler)) {
|
||||
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
|
||||
}
|
||||
|
||||
if (!view.dataset) {
|
||||
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
|
||||
}
|
||||
}
|
||||
|
||||
_panels[panel.id] = panel;
|
||||
|
||||
if (action) {
|
||||
let messageType;
|
||||
|
||||
switch(action) {
|
||||
case this.Action.INSTALL:
|
||||
messageType = "HomePanels:Install";
|
||||
break;
|
||||
|
||||
case this.Action.REFRESH:
|
||||
messageType = "HomePanels:Refresh";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
type: messageType,
|
||||
panel: _panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
if (!(id in _panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
|
||||
let panel = _panels[id];
|
||||
delete _panels[id];
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
panel: _panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// Public API
|
||||
this.Home = Object.freeze({
|
||||
|
41
mobile/android/modules/Messaging.jsm
Normal file
@ -0,0 +1,41 @@
|
||||
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["sendMessageToJava"];
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
|
||||
function sendMessageToJava(aMessage, aCallback) {
|
||||
if (aCallback) {
|
||||
let id = uuidgen.generateUUID().toString();
|
||||
let obs = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
if (data.__guid__ != id) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(obs, aMessage.type + ":Return", false);
|
||||
Services.obs.removeObserver(obs, aMessage.type + ":Error", false);
|
||||
|
||||
aCallback(aTopic == aMessage.type + ":Return" ? aData : null,
|
||||
aTopic == aMessage.type + ":Error" ? aData : null)
|
||||
}
|
||||
}
|
||||
|
||||
aMessage.__guid__ = id;
|
||||
Services.obs.addObserver(obs, aMessage.type + ":Return", false);
|
||||
Services.obs.addObserver(obs, aMessage.type + ":Error", false);
|
||||
}
|
||||
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
|
||||
}
|
@ -10,10 +10,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
// For adding observers.
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
|
||||
}
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
let _callbackId = 1;
|
||||
|
||||
|
@ -7,6 +7,7 @@ let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Prompt"];
|
||||
|
||||
@ -36,8 +37,6 @@ function Prompt(aOptions) {
|
||||
this.msg.hint = aOptions.hint;
|
||||
|
||||
let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
this.guid = idService.generateUUID().toString();
|
||||
this.msg.guid = this.guid;
|
||||
}
|
||||
|
||||
Prompt.prototype = {
|
||||
@ -152,24 +151,20 @@ Prompt.prototype = {
|
||||
show: function(callback) {
|
||||
this.callback = callback;
|
||||
log("Sending message");
|
||||
Services.obs.addObserver(this, "Prompt:Reply", false);
|
||||
Services.obs.addObserver(this, "Prompt:Return", false);
|
||||
this._innerShow();
|
||||
},
|
||||
|
||||
_innerShow: function() {
|
||||
Services.androidBridge.handleGeckoMessage(JSON.stringify(this.msg));
|
||||
},
|
||||
sendMessageToJava(this.msg, (aData) => {
|
||||
log("observe " + aData);
|
||||
let data = JSON.parse(aData);
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
log("observe " + aData);
|
||||
let data = JSON.parse(aData);
|
||||
if (data.guid != this.guid)
|
||||
return;
|
||||
Services.obs.removeObserver(this, "Prompt:Return", false);
|
||||
|
||||
Services.obs.removeObserver(this, "Prompt:Reply", false);
|
||||
|
||||
if (this.callback)
|
||||
this.callback(data);
|
||||
if (this.callback)
|
||||
this.callback(data);
|
||||
});
|
||||
},
|
||||
|
||||
_setListItems: function(aItems) {
|
||||
|
@ -11,15 +11,12 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/LoadContextInfo.jsm");
|
||||
Cu.import("resource://gre/modules/FormHistory.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
function dump(a) {
|
||||
Services.console.logStringMessage(a);
|
||||
}
|
||||
|
||||
function sendMessageToJava(aMessage) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
|
||||
}
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Sanitizer"];
|
||||
|
||||
let downloads = {
|
||||
|
@ -11,10 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
// For adding observers.
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
|
||||
}
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
/**
|
||||
* Create an interface to an Android SharedPreferences branch.
|
||||
@ -62,13 +59,21 @@ SharedPreferences.prototype = Object.freeze({
|
||||
this._setOne(prefName, value, "int");
|
||||
},
|
||||
|
||||
_get: function _get(prefs) {
|
||||
let values = sendMessageToJava({
|
||||
_get: function _get(prefs, callback) {
|
||||
let result = null;
|
||||
sendMessageToJava({
|
||||
type: "SharedPreferences:Get",
|
||||
preferences: prefs,
|
||||
branch: this._branch,
|
||||
}, (data) => {
|
||||
result = JSON.parse(data).values;
|
||||
});
|
||||
return JSON.parse(values);
|
||||
|
||||
let thread = Services.tm.currentThread;
|
||||
while (result == null)
|
||||
thread.processNextEvent(true);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_getOne: function _getOne(prefName, type) {
|
||||
|
@ -284,10 +284,9 @@ this.WebappManager = {
|
||||
}
|
||||
|
||||
// Map APK names to APK versions.
|
||||
let apkNameToVersion = JSON.parse(sendMessageToJava({
|
||||
type: "Webapps:GetApkVersions",
|
||||
packageNames: installedApps.map(app => app.packageName).filter(packageName => !!packageName)
|
||||
}));
|
||||
let apkNameToVersion = yield this._getAPKVersions(installedApps.map(app =>
|
||||
app.packageName).filter(packageName => !!packageName)
|
||||
);
|
||||
|
||||
// Map manifest URLs to APK versions, which is what the service needs
|
||||
// in order to tell us which apps are outdated; and also map them to app
|
||||
@ -336,6 +335,17 @@ this.WebappManager = {
|
||||
}
|
||||
}).bind(this)); },
|
||||
|
||||
_getAPKVersions: function(packageNames) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
sendMessageToJava({
|
||||
type: "Webapps:GetApkVersions",
|
||||
packageNames: packageNames
|
||||
}, data => deferred.resolve(JSON.parse(data).versions));
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_getInstalledApps: function() {
|
||||
let deferred = Promise.defer();
|
||||
DOMApplicationRegistry.getAll(apps => deferred.resolve(apps));
|
||||
|
@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
|
||||
'HomeProvider.jsm',
|
||||
'JNI.jsm',
|
||||
'LightweightThemeConsumer.jsm',
|
||||
'Messaging.jsm',
|
||||
'Notifications.jsm',
|
||||
'OrderedBroadcast.jsm',
|
||||
'Prompt.jsm',
|
||||
|
@ -305,25 +305,6 @@ Java_org_mozilla_gecko_GeckoAppShell_cameraCallbackBridge(JNIEnv * arg0, jclass
|
||||
|
||||
#ifdef JNI_STUBS
|
||||
|
||||
typedef void (*Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult_t)(JNIEnv *, jclass, jstring, jlong);
|
||||
static Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult_t f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult;
|
||||
extern "C" NS_EXPORT void JNICALL
|
||||
Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv * arg0, jclass arg1, jstring arg2, jlong arg3) {
|
||||
if (!f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult) {
|
||||
arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
|
||||
"JNI Function called before it was loaded");
|
||||
return ;
|
||||
}
|
||||
f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JNI_BINDINGS
|
||||
xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult", &f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult);
|
||||
#endif
|
||||
|
||||
#ifdef JNI_STUBS
|
||||
|
||||
typedef jdouble (*Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t)(JNIEnv *, jclass);
|
||||
static Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime;
|
||||
extern "C" NS_EXPORT jdouble JNICALL
|
||||
|
@ -51,6 +51,10 @@
|
||||
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
|
||||
<command id="cmd_findPrevious"
|
||||
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
|
||||
#ifdef XP_MACOSX
|
||||
<command id="cmd_findSelection"
|
||||
oncommand="document.getElementById('FindToolbar').onFindSelectionCommand();"/>
|
||||
#endif
|
||||
<command id="cmd_reload" oncommand="ViewSourceReload();"/>
|
||||
<command id="cmd_goToLine" oncommand="ViewSourceGoToLine();" disabled="true"/>
|
||||
<command id="cmd_highlightSyntax" oncommand="highlightSyntax();"/>
|
||||
@ -86,6 +90,9 @@
|
||||
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
|
||||
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
|
||||
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
|
||||
#endif
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
|
||||
|
||||
|
@ -58,6 +58,7 @@
|
||||
|
||||
// Simulate typical input
|
||||
textbox.focus();
|
||||
gFindBar.clear();
|
||||
sendChar("m");
|
||||
ok(gFindBar.canClear, "canClear property true after input");
|
||||
let preSelection = gBrowser.contentWindow.getSelection();
|
||||
|
@ -30,6 +30,9 @@
|
||||
var gFindBar = null;
|
||||
var gBrowser;
|
||||
|
||||
var gClipboard = Cc["@mozilla.org/widget/clipboard;1"].getService("nsIClipboard");
|
||||
var gHasFindClipboard = gClipboard.supportsFindClipboard();
|
||||
|
||||
var gStatusText;
|
||||
var gXULBrowserWindow = {
|
||||
QueryInterface: function(aIID) {
|
||||
@ -94,7 +97,8 @@
|
||||
testFindbarSelection();
|
||||
testDrop();
|
||||
testQuickFindLink();
|
||||
testStatusText();
|
||||
if (gHasFindClipboard)
|
||||
testStatusText();
|
||||
testQuickFindClose();
|
||||
}
|
||||
|
||||
@ -104,8 +108,12 @@
|
||||
ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName);
|
||||
ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
|
||||
"testFindbarSelection: find field is not focused: " + aTestName);
|
||||
ok(gFindBar._findField.value == aExpSelection,
|
||||
"Incorrect selection in testFindbarSelection: " + aTestName + ". Selection: " + gFindBar._findField.value);
|
||||
if (gHasFindClipboard) {
|
||||
ok(gFindBar._findField.value == aExpSelection,
|
||||
"Incorrect selection in testFindbarSelection: " + aTestName +
|
||||
". Selection: " + gFindBar._findField.value);
|
||||
}
|
||||
testClipboardSearchString(aExpSelection);
|
||||
|
||||
// Clear the value, close the findbar
|
||||
gFindBar._findField.value = "";
|
||||
@ -185,12 +193,14 @@
|
||||
enterStringIntoFindField(searchStr);
|
||||
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() == searchStr,
|
||||
"testNormalFind: failed to find '" + searchStr + "'");
|
||||
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
|
||||
|
||||
if (!matchCaseCheckbox.hidden) {
|
||||
matchCaseCheckbox.click();
|
||||
enterStringIntoFindField("t");
|
||||
ok(gBrowser.contentWindow.getSelection() != searchStr,
|
||||
"testNormalFind: Case-sensitivy is broken '" + searchStr + "'");
|
||||
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
|
||||
matchCaseCheckbox.click();
|
||||
}
|
||||
}
|
||||
@ -228,6 +238,7 @@
|
||||
|
||||
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() != searchStr,
|
||||
"testNormalFindWithComposition: text shouldn't be found during composition");
|
||||
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
|
||||
|
||||
synthesizeText(
|
||||
{ "composition":
|
||||
@ -243,6 +254,7 @@
|
||||
|
||||
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() == searchStr,
|
||||
"testNormalFindWithComposition: text should be found after committing composition");
|
||||
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
|
||||
|
||||
if (clicked) {
|
||||
matchCaseCheckbox.click();
|
||||
@ -302,6 +314,7 @@
|
||||
enterStringIntoFindField(searchStr);
|
||||
ok(gBrowser.contentWindow.getSelection() == searchStr,
|
||||
"testQuickFindLink: failed to find sample link");
|
||||
testClipboardSearchString(searchStr);
|
||||
}
|
||||
|
||||
function testQuickFindText() {
|
||||
@ -319,6 +332,18 @@
|
||||
enterStringIntoFindField(SEARCH_TEXT);
|
||||
ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
|
||||
"testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
|
||||
testClipboardSearchString(SEARCH_TEXT);
|
||||
}
|
||||
|
||||
function testClipboardSearchString(aExpected) {
|
||||
if (!gHasFindClipboard)
|
||||
return;
|
||||
|
||||
if (!aExpected)
|
||||
aExpected = "";
|
||||
var searchStr = gFindBar.browser.finder.clipboardSearchString;
|
||||
ok(searchStr == aExpected, "testClipboardSearchString: search string not " +
|
||||
"set to '" + aExpected + "', instead found '" + searchStr + "'");
|
||||
}
|
||||
]]></script>
|
||||
|
||||
|
@ -110,6 +110,13 @@
|
||||
findbar.browser.finder.enableSelection();
|
||||
]]></handler>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<handler event="focus"><![CDATA[
|
||||
let findbar = this.findbar;
|
||||
findbar._onFindFieldFocus();
|
||||
]]></handler>
|
||||
#endif
|
||||
|
||||
<handler event="compositionstart"><![CDATA[
|
||||
// Don't close the find toolbar while IME is composing.
|
||||
let findbar = this.findbar;
|
||||
@ -1045,8 +1052,17 @@
|
||||
userWantsPrefill =
|
||||
prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
|
||||
|
||||
let initialString = (this.prefillWithSelection && userWantsPrefill) ?
|
||||
this._getInitialSelection() : null;
|
||||
let initialString = null;
|
||||
if (this.prefillWithSelection && userWantsPrefill)
|
||||
initialString = this._getInitialSelection();
|
||||
#ifdef XP_MACOSX
|
||||
if (!initialString) {
|
||||
let clipboardSearchString = this.browser.finder.clipboardSearchString;
|
||||
if (clipboardSearchString)
|
||||
initialString = clipboardSearchString;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (initialString)
|
||||
this._findField.value = initialString;
|
||||
|
||||
@ -1102,6 +1118,32 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<!--
|
||||
- Fetches the currently selected text and sets that as the text to search
|
||||
- next. This is a MacOS specific feature.
|
||||
-->
|
||||
<method name="onFindSelectionCommand">
|
||||
<body><![CDATA[
|
||||
let searchString = this.browser.finder.setSearchStringToSelection();
|
||||
if (searchString)
|
||||
this._findField.value = searchString;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_onFindFieldFocus">
|
||||
<body><![CDATA[
|
||||
let clipboardSearchString = this._browser.finder.clipboardSearchString;
|
||||
if (clipboardSearchString && this._findField.value != clipboardSearchString) {
|
||||
this._findField.value = clipboardSearchString;
|
||||
// Changing the search string makes the previous status invalid, so
|
||||
// we better clear it here.
|
||||
this._updateStatusUI();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
#endif
|
||||
|
||||
<!--
|
||||
- This handles all the result changes for both
|
||||
- type-ahead-find and highlighting.
|
||||
@ -1119,6 +1161,8 @@
|
||||
<method name="onFindResult">
|
||||
<parameter name="aData"/>
|
||||
<body><![CDATA[
|
||||
if (this._findField.value != this.browser.finder.searchString)
|
||||
this._findField.value = this.browser.finder.searchString;
|
||||
this._updateStatusUI(aData.result, aData.findBackwards);
|
||||
this._updateStatusUIBar(aData.linkURL);
|
||||
|
||||
|
@ -71,6 +71,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||
<!ENTITY findAgainCmd.accesskey "g">
|
||||
<!ENTITY findAgainCmd.commandkey "g">
|
||||
<!ENTITY findAgainCmd.commandkey2 "VK_F3">
|
||||
<!ENTITY findSelectionCmd.commandkey "e">
|
||||
|
||||
<!ENTITY backCmd.label "Back">
|
||||
<!ENTITY backCmd.accesskey "B">
|
||||
|
@ -5,11 +5,6 @@
|
||||
/**
|
||||
* Managing safe shutdown of asynchronous services.
|
||||
*
|
||||
* THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE WITHOUT PRIOR NOTICE
|
||||
* IF YOUR CODE USES IT, IT MAY HAVE STOPPED WORKING ALREADY
|
||||
* YOU HAVE BEEN WARNED
|
||||
*
|
||||
*
|
||||
* Firefox shutdown is composed of phases that take place
|
||||
* sequentially. Typically, each shutdown phase removes some
|
||||
* capabilities from the application. For instance, at the end of
|
||||
|
@ -12,6 +12,16 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Geometry.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
|
||||
"@mozilla.org/intl/texttosuburi;1",
|
||||
"nsITextToSubURI");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
|
||||
"@mozilla.org/widget/clipboard;1",
|
||||
"nsIClipboard");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
|
||||
function Finder(docShell) {
|
||||
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
|
||||
this._fastFind.init(docShell);
|
||||
@ -38,6 +48,7 @@ Finder.prototype = {
|
||||
|
||||
_notify: function (aSearchString, aResult, aFindBackwards, aDrawOutline) {
|
||||
this._searchString = aSearchString;
|
||||
this.clipboardSearchString = aSearchString
|
||||
this._outlineLink(aDrawOutline);
|
||||
|
||||
let foundLink = this._fastFind.foundLink;
|
||||
@ -48,12 +59,7 @@ Finder.prototype = {
|
||||
if (ownerDoc)
|
||||
docCharset = ownerDoc.characterSet;
|
||||
|
||||
if (!this._textToSubURIService) {
|
||||
this._textToSubURIService = Cc["@mozilla.org/intl/texttosuburi;1"]
|
||||
.getService(Ci.nsITextToSubURI);
|
||||
}
|
||||
|
||||
linkURL = this._textToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
|
||||
linkURL = TextToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
|
||||
}
|
||||
|
||||
let data = {
|
||||
@ -70,9 +76,48 @@ Finder.prototype = {
|
||||
},
|
||||
|
||||
get searchString() {
|
||||
if (!this._searchString && this._fastFind.searchString)
|
||||
this._searchString = this._fastFind.searchString;
|
||||
return this._searchString;
|
||||
},
|
||||
|
||||
get clipboardSearchString() {
|
||||
let searchString = "";
|
||||
if (!Clipboard.supportsFindClipboard())
|
||||
return searchString;
|
||||
|
||||
try {
|
||||
let trans = Cc["@mozilla.org/widget/transferable;1"]
|
||||
.createInstance(Ci.nsITransferable);
|
||||
trans.init(this._getWindow()
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsILoadContext));
|
||||
trans.addDataFlavor("text/unicode");
|
||||
|
||||
Clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);
|
||||
|
||||
let data = {};
|
||||
let dataLen = {};
|
||||
trans.getTransferData("text/unicode", data, dataLen);
|
||||
if (data.value) {
|
||||
data = data.value.QueryInterface(Ci.nsISupportsString);
|
||||
searchString = data.toString();
|
||||
}
|
||||
} catch (ex) {}
|
||||
|
||||
return searchString;
|
||||
},
|
||||
|
||||
set clipboardSearchString(aSearchString) {
|
||||
if (!aSearchString || !Clipboard.supportsFindClipboard())
|
||||
return;
|
||||
|
||||
ClipboardHelper.copyStringToClipboard(aSearchString,
|
||||
Ci.nsIClipboard.kFindClipboard,
|
||||
this._getWindow().document);
|
||||
},
|
||||
|
||||
set caseSensitive(aSensitive) {
|
||||
this._fastFind.caseSensitive = aSensitive;
|
||||
},
|
||||
@ -105,6 +150,25 @@ Finder.prototype = {
|
||||
this._notify(searchString, result, aFindBackwards, aDrawOutline);
|
||||
},
|
||||
|
||||
/**
|
||||
* Forcibly set the search string of the find clipboard to the currently
|
||||
* selected text in the window, on supported platforms (i.e. OSX).
|
||||
*/
|
||||
setSearchStringToSelection: function() {
|
||||
// Find the selected text.
|
||||
let selection = this._getWindow().getSelection();
|
||||
// Don't go for empty selections.
|
||||
if (!selection.rangeCount)
|
||||
return null;
|
||||
let searchString = (selection.toString() || "").trim();
|
||||
// Empty strings are rather useless to search for.
|
||||
if (!searchString.length)
|
||||
return null;
|
||||
|
||||
this.clipboardSearchString = searchString;
|
||||
return searchString;
|
||||
},
|
||||
|
||||
highlight: function (aHighlight, aWord) {
|
||||
let found = this._highlight(aHighlight, aWord, null);
|
||||
if (aHighlight) {
|
||||
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 117 B |
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 133 B |
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 227 B |
@ -56,71 +56,69 @@ panel[type="arrow"] {
|
||||
|
||||
panel[type="arrow"][side="top"],
|
||||
panel[type="arrow"][side="bottom"] {
|
||||
margin-left: -26px;
|
||||
margin-right: -26px;
|
||||
margin-left: -25px;
|
||||
margin-right: -25px;
|
||||
}
|
||||
|
||||
panel[type="arrow"][side="left"],
|
||||
panel[type="arrow"][side="right"] {
|
||||
margin-top: -26px;
|
||||
margin-bottom: -26px;
|
||||
margin-top: -25px;
|
||||
margin-bottom: -25px;
|
||||
}
|
||||
|
||||
.panel-arrowcontent {
|
||||
-moz-appearance: none;
|
||||
background: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 0 1px rgba(255,255,255,.5) inset,
|
||||
0 1px 0 rgba(255,255,255,.05) inset,
|
||||
0 0 0 1px rgba(0,0,0,.25);
|
||||
color: #333;
|
||||
background-image: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
|
||||
border-radius: 3.5px;
|
||||
box-shadow: 0 0 0 1px hsla(210,4%,10%,.05);
|
||||
color: hsl(0,0%,10%);
|
||||
padding: 16px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.panel-arrow[side="top"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-bottom: -2px;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.panel-arrow[side="bottom"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
|
||||
-moz-transform: scaleY(-1);
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-top: -2px;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.panel-arrow[side="left"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
margin-right: -2px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.panel-arrow[side="right"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
|
||||
transform: scaleX(-1);
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
margin-left: -2px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.panel-arrow[side="top"],
|
||||
.panel-arrow[side="bottom"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical@2x.png");
|
||||
width: 39px;
|
||||
height: 16px;
|
||||
width: 18px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.panel-arrow[side="left"],
|
||||
.panel-arrow[side="right"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal@2x.png");
|
||||
width: 16px;
|
||||
height: 39px;
|
||||
width: 10px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,6 +159,6 @@ XXX: apply styles to all themes until bug 509642 is fixed
|
||||
%endif
|
||||
|
||||
.popup-notification-closebutton {
|
||||
-moz-margin-end: -14px;
|
||||
margin-top: -10px;
|
||||
-moz-margin-end: -8px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
@ -42,8 +42,6 @@ using namespace mozilla;
|
||||
using namespace mozilla::widget::android;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
NS_IMPL_ISUPPORTS0(nsFilePickerCallback)
|
||||
|
||||
StaticRefPtr<AndroidBridge> AndroidBridge::sBridge;
|
||||
static unsigned sJavaEnvThreadIndex = 0;
|
||||
static jobject sGlobalContext = nullptr;
|
||||
@ -486,41 +484,6 @@ AndroidBridge::GetScreenDepth()
|
||||
return sDepth;
|
||||
}
|
||||
|
||||
void
|
||||
AndroidBridge::ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions)
|
||||
{
|
||||
JNIEnv *env = GetJNIEnv();
|
||||
|
||||
AutoLocalJNIFrame jniFrame(env, 1);
|
||||
jstring jstr = GeckoAppShell::ShowFilePickerForExtensionsWrapper(aExtensions);
|
||||
if (jstr == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
aFilePath.Assign(nsJNIString(jstr, env));
|
||||
}
|
||||
|
||||
void
|
||||
AndroidBridge::ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType)
|
||||
{
|
||||
JNIEnv *env = GetJNIEnv();
|
||||
|
||||
AutoLocalJNIFrame jniFrame(env, 1);
|
||||
jstring jstr = GeckoAppShell::ShowFilePickerForMimeTypeWrapper(aMimeType);
|
||||
if (jstr == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
aFilePath.Assign(nsJNIString(jstr, env));
|
||||
}
|
||||
|
||||
void
|
||||
AndroidBridge::ShowFilePickerAsync(const nsAString& aMimeType, nsFilePickerCallback* callback)
|
||||
{
|
||||
callback->AddRef();
|
||||
GeckoAppShell::ShowFilePickerAsyncWrapper(aMimeType, (int64_t) callback);
|
||||
}
|
||||
|
||||
void
|
||||
AndroidBridge::Vibrate(const nsTArray<uint32_t>& aPattern)
|
||||
{
|
||||
@ -999,21 +962,14 @@ AndroidBridge::GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInf
|
||||
}
|
||||
|
||||
void
|
||||
AndroidBridge::HandleGeckoMessage(const nsAString &aMessage, nsAString &aRet)
|
||||
AndroidBridge::HandleGeckoMessage(const nsAString &aMessage)
|
||||
{
|
||||
ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
|
||||
|
||||
JNIEnv *env = GetJNIEnv();
|
||||
|
||||
AutoLocalJNIFrame jniFrame(env, 1);
|
||||
jstring returnMessage = GeckoAppShell::HandleGeckoMessageWrapper(aMessage);
|
||||
|
||||
if (!returnMessage)
|
||||
return;
|
||||
|
||||
nsJNIString jniStr(returnMessage, env);
|
||||
aRet.Assign(jniStr);
|
||||
ALOG_BRIDGE("leaving %s", __PRETTY_FUNCTION__);
|
||||
GeckoAppShell::HandleGeckoMessageWrapper(aMessage);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1534,9 +1490,9 @@ nsAndroidBridge::~nsAndroidBridge()
|
||||
}
|
||||
|
||||
/* void handleGeckoEvent (in AString message); */
|
||||
NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(const nsAString & message, nsAString &aRet)
|
||||
NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(const nsAString & message)
|
||||
{
|
||||
AndroidBridge::Bridge()->HandleGeckoMessage(message, aRet);
|
||||
AndroidBridge::Bridge()->HandleGeckoMessage(message);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -282,7 +282,7 @@ public:
|
||||
bool LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride);
|
||||
bool UnlockWindow(void *window);
|
||||
|
||||
void HandleGeckoMessage(const nsAString& message, nsAString &aRet);
|
||||
void HandleGeckoMessage(const nsAString& message);
|
||||
|
||||
bool InitCamera(const nsCString& contentType, uint32_t camera, uint32_t *width, uint32_t *height, uint32_t *fps);
|
||||
|
||||
|
@ -81,9 +81,6 @@ jmethodID GeckoAppShell::jSetFullScreen = 0;
|
||||
jmethodID GeckoAppShell::jSetKeepScreenOn = 0;
|
||||
jmethodID GeckoAppShell::jSetURITitle = 0;
|
||||
jmethodID GeckoAppShell::jShowAlertNotificationWrapper = 0;
|
||||
jmethodID GeckoAppShell::jShowFilePickerAsyncWrapper = 0;
|
||||
jmethodID GeckoAppShell::jShowFilePickerForExtensionsWrapper = 0;
|
||||
jmethodID GeckoAppShell::jShowFilePickerForMimeTypeWrapper = 0;
|
||||
jmethodID GeckoAppShell::jShowInputMethodPicker = 0;
|
||||
jmethodID GeckoAppShell::jUnlockProfile = 0;
|
||||
jmethodID GeckoAppShell::jUnlockScreenOrientation = 0;
|
||||
@ -132,7 +129,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
|
||||
jGetScreenOrientationWrapper = getStaticMethod("getScreenOrientation", "()S");
|
||||
jGetShowPasswordSetting = getStaticMethod("getShowPasswordSetting", "()Z");
|
||||
jGetSystemColoursWrapper = getStaticMethod("getSystemColors", "()[I");
|
||||
jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Ljava/lang/String;)V");
|
||||
jHandleUncaughtException = getStaticMethod("handleUncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");
|
||||
jHideProgressDialog = getStaticMethod("hideProgressDialog", "()V");
|
||||
jInitCameraWrapper = getStaticMethod("initCamera", "(Ljava/lang/String;III)[I");
|
||||
@ -163,9 +160,6 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
|
||||
jSetKeepScreenOn = getStaticMethod("setKeepScreenOn", "(Z)V");
|
||||
jSetURITitle = getStaticMethod("setUriTitle", "(Ljava/lang/String;Ljava/lang/String;)V");
|
||||
jShowAlertNotificationWrapper = getStaticMethod("showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
jShowFilePickerAsyncWrapper = getStaticMethod("showFilePickerAsync", "(Ljava/lang/String;J)V");
|
||||
jShowFilePickerForExtensionsWrapper = getStaticMethod("showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
jShowFilePickerForMimeTypeWrapper = getStaticMethod("showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
jShowInputMethodPicker = getStaticMethod("showInputMethodPicker", "()V");
|
||||
jUnlockProfile = getStaticMethod("unlockProfile", "()Z");
|
||||
jUnlockScreenOrientation = getStaticMethod("unlockScreenOrientation", "()V");
|
||||
@ -703,19 +697,18 @@ jintArray GeckoAppShell::GetSystemColoursWrapper() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
jstring GeckoAppShell::HandleGeckoMessageWrapper(const nsAString& a0) {
|
||||
void GeckoAppShell::HandleGeckoMessageWrapper(const nsAString& a0) {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(2) != 0) {
|
||||
if (env->PushLocalFrame(1) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
jstring j0 = AndroidBridge::NewJavaString(env, a0);
|
||||
|
||||
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jHandleGeckoMessageWrapper, j0);
|
||||
env->CallStaticVoidMethod(mGeckoAppShellClass, jHandleGeckoMessageWrapper, j0);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
|
||||
return ret;
|
||||
env->PopLocalFrame(nullptr);
|
||||
}
|
||||
|
||||
void GeckoAppShell::HandleUncaughtException(jobject a0, jthrowable a1) {
|
||||
@ -1136,50 +1129,6 @@ void GeckoAppShell::ShowAlertNotificationWrapper(const nsAString& a0, const nsAS
|
||||
env->PopLocalFrame(nullptr);
|
||||
}
|
||||
|
||||
void GeckoAppShell::ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1) {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(1) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
jstring j0 = AndroidBridge::NewJavaString(env, a0);
|
||||
|
||||
env->CallStaticVoidMethod(mGeckoAppShellClass, jShowFilePickerAsyncWrapper, j0, a1);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
env->PopLocalFrame(nullptr);
|
||||
}
|
||||
|
||||
jstring GeckoAppShell::ShowFilePickerForExtensionsWrapper(const nsAString& a0) {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(2) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
jstring j0 = AndroidBridge::NewJavaString(env, a0);
|
||||
|
||||
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jShowFilePickerForExtensionsWrapper, j0);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
jstring GeckoAppShell::ShowFilePickerForMimeTypeWrapper(const nsAString& a0) {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(2) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
jstring j0 = AndroidBridge::NewJavaString(env, a0);
|
||||
|
||||
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jShowFilePickerForMimeTypeWrapper, j0);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GeckoAppShell::ShowInputMethodPicker() {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(0) != 0) {
|
||||
|
@ -57,7 +57,7 @@ public:
|
||||
static int16_t GetScreenOrientationWrapper();
|
||||
static bool GetShowPasswordSetting();
|
||||
static jintArray GetSystemColoursWrapper();
|
||||
static jstring HandleGeckoMessageWrapper(const nsAString& a0);
|
||||
static void HandleGeckoMessageWrapper(const nsAString& a0);
|
||||
static void HandleUncaughtException(jobject a0, jthrowable a1);
|
||||
static void HideProgressDialog();
|
||||
static jintArray InitCameraWrapper(const nsAString& a0, int32_t a1, int32_t a2, int32_t a3);
|
||||
@ -88,9 +88,6 @@ public:
|
||||
static void SetKeepScreenOn(bool a0);
|
||||
static void SetURITitle(const nsAString& a0, const nsAString& a1);
|
||||
static void ShowAlertNotificationWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3, const nsAString& a4);
|
||||
static void ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1);
|
||||
static jstring ShowFilePickerForExtensionsWrapper(const nsAString& a0);
|
||||
static jstring ShowFilePickerForMimeTypeWrapper(const nsAString& a0);
|
||||
static void ShowInputMethodPicker();
|
||||
static bool UnlockProfile();
|
||||
static void UnlockScreenOrientation();
|
||||
@ -169,9 +166,6 @@ protected:
|
||||
static jmethodID jSetKeepScreenOn;
|
||||
static jmethodID jSetURITitle;
|
||||
static jmethodID jShowAlertNotificationWrapper;
|
||||
static jmethodID jShowFilePickerAsyncWrapper;
|
||||
static jmethodID jShowFilePickerForExtensionsWrapper;
|
||||
static jmethodID jShowFilePickerForMimeTypeWrapper;
|
||||
static jmethodID jShowInputMethodPicker;
|
||||
static jmethodID jUnlockProfile;
|
||||
static jmethodID jUnlockScreenOrientation;
|
||||
|
@ -109,3 +109,9 @@ nsClipboard::SupportsSelectionClipboard(bool *aIsSupported)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsClipboard::SupportsFindClipboard(bool* _retval)
|
||||
{
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -63,10 +63,10 @@ interface nsIAndroidDisplayport : nsISupports {
|
||||
attribute float resolution;
|
||||
};
|
||||
|
||||
[scriptable, uuid(5aa0cfa5-377c-4f5e-8dcf-59ebd9482d65)]
|
||||
[scriptable, uuid(2af84552-4f74-480d-b637-ce2bcd7148fc)]
|
||||
interface nsIAndroidBridge : nsISupports
|
||||
{
|
||||
AString handleGeckoMessage(in AString message);
|
||||
void handleGeckoMessage(in AString message);
|
||||
attribute nsIAndroidBrowserApp browserApp;
|
||||
nsIAndroidDisplayport getDisplayPort(in boolean aPageSizeUpdate, in boolean isBrowserContentDisplayed, in int32_t tabId, in nsIAndroidViewport metrics);
|
||||
void contentDocumentChanged();
|
||||
|
@ -23,6 +23,7 @@ public:
|
||||
// nsIClipboard
|
||||
NS_IMETHOD HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
|
||||
int32_t aWhichClipboard, bool *_retval);
|
||||
NS_IMETHOD SupportsFindClipboard(bool *_retval);
|
||||
|
||||
// Helper methods, used also by nsDragService
|
||||
static NSDictionary* PasteboardDictFromTransferable(nsITransferable *aTransferable);
|
||||
@ -37,7 +38,12 @@ protected:
|
||||
NS_IMETHOD GetNativeClipboardData(nsITransferable * aTransferable, int32_t aWhichClipboard);
|
||||
|
||||
private:
|
||||
int mChangeCount; // this is always set to the native change count after any clipboard modifications
|
||||
// This is always set to the native change count after any modification of the
|
||||
// general clipboard.
|
||||
int mChangeCountGeneral;
|
||||
// This is always set to the native change count after any modification of the
|
||||
// find clipboard.
|
||||
int mChangeCountFind;
|
||||
|
||||
};
|
||||
|
||||
|
@ -35,7 +35,8 @@ extern void EnsureLogInitialized();
|
||||
|
||||
nsClipboard::nsClipboard() : nsBaseClipboard()
|
||||
{
|
||||
mChangeCount = 0;
|
||||
mChangeCountGeneral = 0;
|
||||
mChangeCountFind = 0;
|
||||
|
||||
EnsureLogInitialized();
|
||||
}
|
||||
@ -65,7 +66,7 @@ nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
if ((aWhichClipboard != kGlobalClipboard) || !mTransferable)
|
||||
if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
mIgnoreEmptyNotification = true;
|
||||
@ -74,28 +75,44 @@ nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
|
||||
if (!pasteboardOutputDict)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// write everything out to the general pasteboard
|
||||
unsigned int outputCount = [pasteboardOutputDict count];
|
||||
NSArray* outputKeys = [pasteboardOutputDict allKeys];
|
||||
NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
|
||||
[generalPBoard declareTypes:outputKeys owner:nil];
|
||||
NSPasteboard* cocoaPasteboard;
|
||||
if (aWhichClipboard == kFindClipboard) {
|
||||
cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
|
||||
[cocoaPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
||||
} else {
|
||||
// Write everything else out to the general pasteboard.
|
||||
cocoaPasteboard = [NSPasteboard generalPasteboard];
|
||||
[cocoaPasteboard declareTypes:outputKeys owner:nil];
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < outputCount; i++) {
|
||||
NSString* currentKey = [outputKeys objectAtIndex:i];
|
||||
id currentValue = [pasteboardOutputDict valueForKey:currentKey];
|
||||
if (currentKey == NSStringPboardType ||
|
||||
currentKey == kCorePboardType_url ||
|
||||
currentKey == kCorePboardType_urld ||
|
||||
currentKey == kCorePboardType_urln) {
|
||||
[generalPBoard setString:currentValue forType:currentKey];
|
||||
} else if (currentKey == NSHTMLPboardType) {
|
||||
[generalPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
|
||||
forType:currentKey];
|
||||
if (aWhichClipboard == kFindClipboard) {
|
||||
if (currentKey == NSStringPboardType)
|
||||
[cocoaPasteboard setString:currentValue forType:currentKey];
|
||||
} else {
|
||||
[generalPBoard setData:currentValue forType:currentKey];
|
||||
if (currentKey == NSStringPboardType ||
|
||||
currentKey == kCorePboardType_url ||
|
||||
currentKey == kCorePboardType_urld ||
|
||||
currentKey == kCorePboardType_urln) {
|
||||
[cocoaPasteboard setString:currentValue forType:currentKey];
|
||||
} else if (currentKey == NSHTMLPboardType) {
|
||||
[cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
|
||||
forType:currentKey];
|
||||
} else {
|
||||
[cocoaPasteboard setData:currentValue forType:currentKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mChangeCount = [generalPBoard changeCount];
|
||||
if (aWhichClipboard == kFindClipboard) {
|
||||
mChangeCountFind = [cocoaPasteboard changeCount];
|
||||
} else {
|
||||
mChangeCountGeneral = [cocoaPasteboard changeCount];
|
||||
}
|
||||
|
||||
mIgnoreEmptyNotification = false;
|
||||
|
||||
@ -238,10 +255,15 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
if ((aWhichClipboard != kGlobalClipboard) || !aTransferable)
|
||||
if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
NSPasteboard* cocoaPasteboard = [NSPasteboard generalPasteboard];
|
||||
NSPasteboard* cocoaPasteboard;
|
||||
if (aWhichClipboard == kFindClipboard) {
|
||||
cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
|
||||
} else {
|
||||
cocoaPasteboard = [NSPasteboard generalPasteboard];
|
||||
}
|
||||
if (!cocoaPasteboard)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
@ -254,9 +276,10 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
|
||||
uint32_t flavorCount;
|
||||
flavorList->Count(&flavorCount);
|
||||
|
||||
int changeCount = (aWhichClipboard == kFindClipboard) ? mChangeCountFind : mChangeCountGeneral;
|
||||
// If we were the last ones to put something on the pasteboard, then just use the cached
|
||||
// transferable. Otherwise clear it because it isn't relevant any more.
|
||||
if (mChangeCount == [cocoaPasteboard changeCount]) {
|
||||
if (changeCount == [cocoaPasteboard changeCount]) {
|
||||
if (mTransferable) {
|
||||
for (uint32_t i = 0; i < flavorCount; i++) {
|
||||
nsCOMPtr<nsISupports> genericFlavor;
|
||||
@ -277,9 +300,8 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
nsBaseClipboard::EmptyClipboard(kGlobalClipboard);
|
||||
} else {
|
||||
nsBaseClipboard::EmptyClipboard(aWhichClipboard);
|
||||
}
|
||||
|
||||
// at this point we can't satisfy the request from cache data so let's look
|
||||
@ -358,6 +380,14 @@ nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsClipboard::SupportsFindClipboard(bool *_retval)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(_retval);
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// This function converts anything that other applications might understand into the system format
|
||||
// and puts it into a dictionary which it returns.
|
||||
// static
|
||||
|
@ -448,6 +448,13 @@ nsClipboard::SupportsSelectionClipboard(bool *_retval)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsClipboard::SupportsFindClipboard(bool* _retval)
|
||||
{
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
GdkAtom
|
||||
nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
|
||||
|
@ -11,11 +11,12 @@
|
||||
|
||||
interface nsIArray;
|
||||
|
||||
[scriptable, uuid(38984945-8674-4d04-b786-5c0ca9434457)]
|
||||
[scriptable, uuid(ceaa0047-647f-4b8e-ad1c-aff9fa62aa51)]
|
||||
interface nsIClipboard : nsISupports
|
||||
{
|
||||
const long kSelectionClipboard = 0;
|
||||
const long kGlobalClipboard = 1;
|
||||
const long kFindClipboard = 2;
|
||||
|
||||
/**
|
||||
* Given a transferable, set the data on the native clipboard
|
||||
@ -73,6 +74,14 @@ interface nsIClipboard : nsISupports
|
||||
* @result NS_OK if successful.
|
||||
*/
|
||||
boolean supportsSelectionClipboard ( ) ;
|
||||
|
||||
/**
|
||||
* Allows clients to determine if the implementation supports the concept of a
|
||||
* separate clipboard for find search strings.
|
||||
*
|
||||
* @result NS_OK if successful.
|
||||
*/
|
||||
boolean supportsFindClipboard ( ) ;
|
||||
};
|
||||
|
||||
|
||||
|
@ -556,3 +556,12 @@ nsClipboard::SupportsSelectionClipboard(bool *_retval)
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsClipboard::SupportsFindClipboard(bool* _retval)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(_retval);
|
||||
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ nsBaseClipboard::~nsBaseClipboard()
|
||||
{
|
||||
EmptyClipboard(kSelectionClipboard);
|
||||
EmptyClipboard(kGlobalClipboard);
|
||||
EmptyClipboard(kFindClipboard);
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(nsBaseClipboard, nsIClipboard)
|
||||
@ -39,7 +40,9 @@ NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable * aTransferable, nsIClipb
|
||||
return NS_OK;
|
||||
bool selectClipPresent;
|
||||
SupportsSelectionClipboard(&selectClipPresent);
|
||||
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
bool findClipPresent;
|
||||
SupportsFindClipboard(&findClipPresent);
|
||||
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
mEmptyingForSetData = true;
|
||||
@ -75,10 +78,12 @@ NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable * aTransferable, nsIClipb
|
||||
NS_IMETHODIMP nsBaseClipboard::GetData(nsITransferable * aTransferable, int32_t aWhichClipboard)
|
||||
{
|
||||
NS_ASSERTION ( aTransferable, "clipboard given a null transferable" );
|
||||
|
||||
|
||||
bool selectClipPresent;
|
||||
SupportsSelectionClipboard(&selectClipPresent);
|
||||
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
bool findClipPresent;
|
||||
SupportsFindClipboard(&findClipPresent);
|
||||
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if ( aTransferable )
|
||||
@ -91,7 +96,9 @@ NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard)
|
||||
{
|
||||
bool selectClipPresent;
|
||||
SupportsSelectionClipboard(&selectClipPresent);
|
||||
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
bool findClipPresent;
|
||||
SupportsFindClipboard(&findClipPresent);
|
||||
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (mIgnoreEmptyNotification)
|
||||
@ -123,3 +130,10 @@ nsBaseClipboard::SupportsSelectionClipboard(bool* _retval)
|
||||
*_retval = false; // we don't support the selection clipboard by default.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBaseClipboard::SupportsFindClipboard(bool* _retval)
|
||||
{
|
||||
*_retval = false; // we don't support the find clipboard by default.
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -51,16 +51,25 @@ nsClipboardHelper::CopyStringToClipboard(const nsAString& aString,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(clipboard, NS_ERROR_FAILURE);
|
||||
|
||||
bool clipboardSupported;
|
||||
// don't go any further if they're asking for the selection
|
||||
// clipboard on a platform which doesn't support it (i.e., unix)
|
||||
if (nsIClipboard::kSelectionClipboard == aClipboardID) {
|
||||
bool clipboardSupported;
|
||||
rv = clipboard->SupportsSelectionClipboard(&clipboardSupported);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!clipboardSupported)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// don't go any further if they're asking for the find clipboard on a platform
|
||||
// which doesn't support it (i.e., non-osx)
|
||||
if (nsIClipboard::kFindClipboard == aClipboardID) {
|
||||
rv = clipboard->SupportsFindClipboard(&clipboardSupported);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!clipboardSupported)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// create a transferable for putting data on the clipboard
|
||||
nsCOMPtr<nsITransferable>
|
||||
trans(do_CreateInstance("@mozilla.org/widget/transferable;1", &rv));
|
||||
|
@ -91,3 +91,10 @@ nsClipboardProxy::SupportsSelectionClipboard(bool *aIsSupported)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsClipboardProxy::SupportsFindClipboard(bool *aIsSupported)
|
||||
{
|
||||
*aIsSupported = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|