Bug 750903 - Land browser/metro/* r=mbrubeck
authorJim Mathies <jmathies@mozilla.com>
Tue, 12 Feb 2013 14:51:25 -0600
changeset 121616 a85a2ddb41bf1749f019374adbea00cb28981ad5
parent 121615 fcb476f5242924257205e4c4f9c8bf576b6c1f40
child 121617 5f0882ee58c05ea522aa65a7467cfd422665c0e9
push id24298
push userjmathies@mozilla.com
push dateTue, 12 Feb 2013 20:56:08 +0000
treeherdermozilla-central@8fcb6057ac39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs750903
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 750903 - Land browser/metro/* r=mbrubeck
browser/metro/Makefile.in
browser/metro/base/Makefile.in
browser/metro/base/content/AnimatedZoom.js
browser/metro/base/content/BrowserTouchHandler.js
browser/metro/base/content/ContextCommands.js
browser/metro/base/content/LoginManagerChild.js
browser/metro/base/content/PageActions.js
browser/metro/base/content/RemoteTabs.js
browser/metro/base/content/TopSites.js
browser/metro/base/content/Util.js
browser/metro/base/content/WebProgress.js
browser/metro/base/content/appbar.js
browser/metro/base/content/bindings/appbar.xml
browser/metro/base/content/bindings/arrowbox.xml
browser/metro/base/content/bindings/autocomplete.xml
browser/metro/base/content/bindings/bindings.xml
browser/metro/base/content/bindings/browser.js
browser/metro/base/content/bindings/browser.xml
browser/metro/base/content/bindings/console.xml
browser/metro/base/content/bindings/dialog.xml
browser/metro/base/content/bindings/downloads.xml
browser/metro/base/content/bindings/flyoutpanel.xml
browser/metro/base/content/bindings/grid.xml
browser/metro/base/content/bindings/pageaction.xml
browser/metro/base/content/bindings/selectionoverlay.xml
browser/metro/base/content/bindings/tabs.xml
browser/metro/base/content/bindings/toggleswitch.xml
browser/metro/base/content/bookmarks.js
browser/metro/base/content/browser-scripts.js
browser/metro/base/content/browser-ui.js
browser/metro/base/content/browser.css
browser/metro/base/content/browser.js
browser/metro/base/content/browser.xul
browser/metro/base/content/commandUtil.js
browser/metro/base/content/config.js
browser/metro/base/content/config.xul
browser/metro/base/content/console.js
browser/metro/base/content/contenthandlers/ConsoleAPIObserver.js
browser/metro/base/content/contenthandlers/Content.js
browser/metro/base/content/contenthandlers/ContextMenuHandler.js
browser/metro/base/content/contenthandlers/FindHandler.js
browser/metro/base/content/contenthandlers/FormHelper.js
browser/metro/base/content/contenthandlers/PluginCTPHandler.js
browser/metro/base/content/contenthandlers/SelectionHandler.js
browser/metro/base/content/contenthandlers/ViewportHandler.js
browser/metro/base/content/cursor.css
browser/metro/base/content/downloads.js
browser/metro/base/content/exceptions.js
browser/metro/base/content/helperui/AlertsHelper.js
browser/metro/base/content/helperui/CaptureDialog.js
browser/metro/base/content/helperui/CapturePickerUI.js
browser/metro/base/content/helperui/CharsetMenu.js
browser/metro/base/content/helperui/FindHelperUI.js
browser/metro/base/content/helperui/FormHelperUI.js
browser/metro/base/content/helperui/IdentityUI.js
browser/metro/base/content/helperui/IndexedDB.js
browser/metro/base/content/helperui/MasterPasswordUI.js
browser/metro/base/content/helperui/MenuUI.js
browser/metro/base/content/helperui/OfflineApps.js
browser/metro/base/content/helperui/SelectHelperUI.js
browser/metro/base/content/helperui/SelectionHelperUI.js
browser/metro/base/content/helperui/SharingUI.js
browser/metro/base/content/history.js
browser/metro/base/content/input.js
browser/metro/base/content/jsshell/shell.html
browser/metro/base/content/jsshell/shell.xul
browser/metro/base/content/pages/aboutCertError.xhtml
browser/metro/base/content/pages/aboutCrash.xhtml
browser/metro/base/content/pages/aboutRights.xhtml
browser/metro/base/content/pages/blockedSite.xhtml
browser/metro/base/content/pages/fullscreen-video.xhtml
browser/metro/base/content/pages/netError.xhtml
browser/metro/base/content/preferences.js
browser/metro/base/content/prompt/CaptureDialog.xul
browser/metro/base/content/prompt/alert.xul
browser/metro/base/content/prompt/confirm.xul
browser/metro/base/content/prompt/masterPassword.xul
browser/metro/base/content/prompt/prompt.js
browser/metro/base/content/prompt/prompt.xul
browser/metro/base/content/prompt/promptPassword.xul
browser/metro/base/content/prompt/removeMasterPassword.xul
browser/metro/base/content/prompt/select.xul
browser/metro/base/content/prompt/share.xul
browser/metro/base/content/sanitize.js
browser/metro/base/content/sync.js
browser/metro/base/content/video.js
browser/metro/base/jar.mn
browser/metro/base/tests/Makefile.in
browser/metro/base/tests/addons/browser_install1_1/bootstrap.js
browser/metro/base/tests/addons/browser_install1_1/install.rdf
browser/metro/base/tests/addons/browser_install1_2/install.rdf
browser/metro/base/tests/addons/browser_install1_3/install.rdf
browser/metro/base/tests/addons/browser_locale1/boostrap.js
browser/metro/base/tests/addons/browser_locale1/chrome.manifest
browser/metro/base/tests/addons/browser_locale1/install.rdf
browser/metro/base/tests/browser_canonizeURL.js
browser/metro/base/tests/browser_context_ui.js
browser/metro/base/tests/browser_downloads.js
browser/metro/base/tests/browser_onscreen_keyboard.html
browser/metro/base/tests/browser_onscreen_keyboard.js
browser/metro/base/tests/browser_plugin_input.html
browser/metro/base/tests/browser_plugin_input_keyboard.js
browser/metro/base/tests/browser_plugin_input_mouse.js
browser/metro/base/tests/browser_remotetabs.js
browser/metro/base/tests/browser_test.js
browser/metro/base/tests/head.js
browser/metro/components/AboutRedirector.js
browser/metro/components/AlertsService.js
browser/metro/components/BrowserCLH.js
browser/metro/components/BrowserStartup.js
browser/metro/components/CapturePicker.js
browser/metro/components/ContentDispatchChooser.js
browser/metro/components/ContentPermissionPrompt.js
browser/metro/components/DirectoryProvider.js
browser/metro/components/DownloadManagerUI.js
browser/metro/components/FormAutoComplete.js
browser/metro/components/HelperAppDialog.js
browser/metro/components/LoginManager.js
browser/metro/components/LoginManagerPrompter.idl
browser/metro/components/LoginManagerPrompter.js
browser/metro/components/Makefile.in
browser/metro/components/PromptService.js
browser/metro/components/SafeBrowsing.js
browser/metro/components/SessionStore.idl
browser/metro/components/SessionStore.js
browser/metro/components/Sidebar.js
browser/metro/components/UpdatePrompt.js
browser/metro/components/components.manifest
browser/metro/defs.mk
browser/metro/locales/Makefile.in
browser/metro/locales/en-US/chrome/aboutCertError.dtd
browser/metro/locales/en-US/chrome/browser.dtd
browser/metro/locales/en-US/chrome/browser.properties
browser/metro/locales/en-US/chrome/checkbox.dtd
browser/metro/locales/en-US/chrome/config.dtd
browser/metro/locales/en-US/chrome/notification.dtd
browser/metro/locales/en-US/chrome/phishing.dtd
browser/metro/locales/en-US/chrome/preferences.dtd
browser/metro/locales/en-US/chrome/prompt.dtd
browser/metro/locales/en-US/chrome/sync.dtd
browser/metro/locales/en-US/chrome/sync.properties
browser/metro/locales/en-US/overrides/passwordmgr.properties
browser/metro/locales/generic/profile/bookmarks.json.in
browser/metro/locales/import/Makefile.in
browser/metro/locales/jar.mn
browser/metro/metroapp.ini.in
browser/metro/modules/Makefile.in
browser/metro/modules/video.jsm
browser/metro/profile/Makefile.in
browser/metro/profile/metro.js
browser/metro/shell/Makefile.in
browser/metro/shell/VisualElementsManifest.xml.in
browser/metro/shell/commandexecutehandler/CEHHelper.cpp
browser/metro/shell/commandexecutehandler/CEHHelper.h
browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
browser/metro/shell/commandexecutehandler/CommandExecuteHandler.def
browser/metro/shell/commandexecutehandler/Makefile.in
browser/metro/shell/linktool/Makefile.in
browser/metro/shell/linktool/linktool.cpp
browser/metro/shell/priconfig.xml
browser/metro/shell/resources.pri
browser/metro/shell/testing/Makefile.in
browser/metro/shell/testing/metrotestharness.cpp
browser/metro/shell/tileresources/Resources.resw
browser/metro/shell/tileresources/layout.resfiles
browser/metro/shell/tileresources/resources.resfiles
browser/metro/theme/Makefile.in
browser/metro/theme/about.css
browser/metro/theme/aboutPage.css
browser/metro/theme/browser.css
browser/metro/theme/config.css
browser/metro/theme/content.css
browser/metro/theme/defines.inc
browser/metro/theme/flyoutpanel.css
browser/metro/theme/forms.css
browser/metro/theme/header.css
browser/metro/theme/images/about-footer.png
browser/metro/theme/images/aboutBackground.jpg
browser/metro/theme/images/alert-downloads-30.png
browser/metro/theme/images/appbar-icons.png
browser/metro/theme/images/arrowbox-down.png
browser/metro/theme/images/arrowbox-horiz.png
browser/metro/theme/images/arrowbox-up.png
browser/metro/theme/images/arrowdown-16.png
browser/metro/theme/images/arrowdowndark-16.png
browser/metro/theme/images/arrowleft-16.png
browser/metro/theme/images/arrowleftdark-16.png
browser/metro/theme/images/arrowright-16.png
browser/metro/theme/images/arrowrightdark-16.png
browser/metro/theme/images/arrowup-16.png
browser/metro/theme/images/arrowupdark-16.png
browser/metro/theme/images/back.png
browser/metro/theme/images/button-bg.png
browser/metro/theme/images/check-30.png
browser/metro/theme/images/check-selected-30.png
browser/metro/theme/images/check-unselected-30.png
browser/metro/theme/images/checkmark-hdpi.png
browser/metro/theme/images/closetab-default.png
browser/metro/theme/images/closetab-tab.png
browser/metro/theme/images/closetab-tabselected.png
browser/metro/theme/images/errorpage-larry-black.png
browser/metro/theme/images/errorpage-larry-white.png
browser/metro/theme/images/errorpage-warning.png
browser/metro/theme/images/favicon-default-32.png
browser/metro/theme/images/firefox-watermark.png
browser/metro/theme/images/flyout-back-button.png
browser/metro/theme/images/forward.png
browser/metro/theme/images/homescreen-blank-hdpi.png
browser/metro/theme/images/homescreen-default-hdpi.png
browser/metro/theme/images/identity-default-hdpi.png
browser/metro/theme/images/identity-ev-hdpi.png
browser/metro/theme/images/identity-icons-generic.png
browser/metro/theme/images/identity-icons-https-ev.png
browser/metro/theme/images/identity-icons-https-mixed.png
browser/metro/theme/images/identity-icons-https.png
browser/metro/theme/images/identity-ssl-hdpi.png
browser/metro/theme/images/loading.png
browser/metro/theme/images/locked-hdpi.png
browser/metro/theme/images/more-active.png
browser/metro/theme/images/mozilla-32.png
browser/metro/theme/images/mute-hdpi.png
browser/metro/theme/images/newtab-default.png
browser/metro/theme/images/panel-dark.png
browser/metro/theme/images/panel-light.png
browser/metro/theme/images/pause-hdpi.png
browser/metro/theme/images/play-hdpi.png
browser/metro/theme/images/popup-bg-hdpi.png
browser/metro/theme/images/popup-selected-item-hdpi.png
browser/metro/theme/images/reload.png
browser/metro/theme/images/scrubber-hdpi.png
browser/metro/theme/images/search-clear-30.png
browser/metro/theme/images/search-glass-30.png
browser/metro/theme/images/section-collapsed-16.png
browser/metro/theme/images/section-expanded-16.png
browser/metro/theme/images/selection-monocle.png
browser/metro/theme/images/stop-hdpi.png
browser/metro/theme/images/tab-arrows.png
browser/metro/theme/images/tab-crop.png
browser/metro/theme/images/tab-overlay.png
browser/metro/theme/images/tab-selection-left.png
browser/metro/theme/images/tab-selection-right.png
browser/metro/theme/images/throbber.png
browser/metro/theme/images/unlocked-hdpi.png
browser/metro/theme/images/unmute-hdpi.png
browser/metro/theme/jar.mn
browser/metro/theme/netError.css
browser/metro/theme/platform.css
browser/metro/theme/touchcontrols.css
new file mode 100644
--- /dev/null
+++ b/browser/metro/Makefile.in
@@ -0,0 +1,51 @@
+# 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/.
+
+DEPTH     = ../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/config.mk
+
+DIRS = \
+	base \
+	components \
+	modules \
+	theme \
+	profile \
+	locales \
+	$(NULL)
+
+ifeq ($(OS_ARCH),WINNT)
+DIRS += shell
+endif
+
+TEST_DIRS += base/tests
+
+include $(topsrcdir)/config/rules.mk
+
+#########################################
+# application.ini
+
+ifdef MOZILLA_OFFICIAL
+DEFINES += -DMOZILLA_OFFICIAL
+endif
+
+GRE_MILESTONE := $(shell tail -n 1 $(topsrcdir)/config/milestone.txt 2>/dev/null || tail -1 $(topsrcdir)/config/milestone.txt)
+GRE_BUILDID := $(shell cat $(DEPTH)/config/buildid)
+DEFINES += -DGRE_MILESTONE=$(GRE_MILESTONE) -DGRE_BUILDID=$(GRE_BUILDID)
+
+# 'application.ini' breaks firefox build config. So we use something different.
+metroapp.ini: metroapp.ini.in $(DEPTH)/config/buildid $(topsrcdir)/config/milestone.txt
+	$(RM) "metroapp.ini"
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $@
+
+libs:: metroapp.ini
+	$(INSTALL) metroapp.ini $(FINAL_TARGET)
+
+#########################################
+
+GARBAGE += metroapp.ini
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/Makefile.in
@@ -0,0 +1,20 @@
+# 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/.
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/config.mk
+
+DIST_SUBDIR=metro
+
+DEFINES += -DAB_CD=$(MOZ_UI_LOCALE) \
+           -DPACKAGE=browser \
+           -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
+           $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/AnimatedZoom.js
@@ -0,0 +1,124 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+/**
+ * Responsible for zooming in to a given view rectangle
+ */
+const AnimatedZoom = {
+  startScale: null,
+
+  /** Starts an animated zoom to zoomRect. */
+  animateTo: function(aZoomRect) {
+    if (!aZoomRect)
+      return;
+
+    this.zoomTo = aZoomRect.clone();
+
+    if (this.animationDuration === undefined)
+      this.animationDuration = Services.prefs.getIntPref("browser.ui.zoom.animationDuration");
+
+    Browser.forceChromeReflow();
+
+    this.start();
+
+    // Check if zooming animations were occuring before.
+    if (!this.zoomRect) {
+      this.updateTo(this.zoomFrom);
+
+      mozRequestAnimationFrame(this);
+
+      let event = document.createEvent("Events");
+      event.initEvent("AnimatedZoomBegin", true, true);
+      window.dispatchEvent(event);
+    }
+  },
+
+  start: function start() {
+    this.tab = Browser.selectedTab;
+    this.browser = this.tab.browser;
+    this.bcr = this.browser.getBoundingClientRect();
+    this.zoomFrom = this.zoomRect || this.getStartRect();
+    this.startScale = this.browser.scale;
+    this.beginTime = mozAnimationStartTime;
+  },
+
+  /** Get the visible rect, in device pixels relative to the content origin. */
+  getStartRect: function getStartRect() {
+    let browser = this.browser;
+    let scroll = browser.getRootView().getPosition();
+    return new Rect(scroll.x, scroll.y, this.bcr.width, this.bcr.height);
+  },
+
+  /** Update the visible rect, in device pixels relative to the content origin. */
+  updateTo: function(nextRect) {
+    // Stop animating if the browser has been destroyed
+    if (typeof this.browser.fuzzyZoom !== "function") {
+      this.reset();
+      return false;
+    }
+
+    let zoomRatio = this.bcr.width / nextRect.width;
+    let scale = this.startScale * zoomRatio;
+    let scrollX = nextRect.left * zoomRatio;
+    let scrollY = nextRect.top * zoomRatio;
+
+    this.browser.fuzzyZoom(scale, scrollX, scrollY);
+
+    this.zoomRect = nextRect;
+    return true;
+  },
+
+  /** Stop animation, zoom to point, and clean up. */
+  finish: function() {
+    if (!this.updateTo(this.zoomTo || this.zoomRect))
+      return;
+
+    // Check whether the zoom limits have changed since the animation started.
+    let browser = this.browser;
+    let finalScale = this.tab.clampZoomLevel(browser.scale);
+    if (browser.scale != finalScale)
+      browser.scale = finalScale; // scale= calls finishFuzzyZoom.
+    else
+      browser.finishFuzzyZoom();
+
+    this.reset();
+    browser._updateCSSViewport();
+  },
+
+  reset: function reset() {
+    this.beginTime = null;
+    this.zoomTo = null;
+    this.zoomFrom = null;
+    this.zoomRect = null;
+    this.startScale = null;
+
+    let event = document.createEvent("Events");
+    event.initEvent("AnimatedZoomEnd", true, true);
+    window.dispatchEvent(event);
+  },
+
+  isZooming: function isZooming() {
+    return this.beginTime != null;
+  },
+
+  sample: function(aTimeStamp) {
+    try {
+      let tdiff = aTimeStamp - this.beginTime;
+      let counter = tdiff / this.animationDuration;
+      if (counter < 1) {
+        // update browser to interpolated rectangle
+        let rect = this.zoomFrom.blend(this.zoomTo, counter);
+        if (this.updateTo(rect))
+          mozRequestAnimationFrame(this);
+      } else {
+        // last cycle already rendered final scaled image, now clean up
+        this.finish();
+      }
+    } catch(e) {
+      this.finish();
+      throw e;
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/BrowserTouchHandler.js
@@ -0,0 +1,91 @@
+/*
+ * BrowserTouchHandler
+ *
+ * Receives touch events from our input event capturing in input.js
+ * and relays appropriate events to content. Also handles requests
+ * from content for UI.
+ */
+
+const BrowserTouchHandler = {
+  _debugEvents: false,
+
+  init: function init() {
+    // Misc. events we catch during the bubbling phase
+    document.addEventListener("PopupChanged", this, false);
+    document.addEventListener("CancelTouchSequence", this, false);
+
+    // Messages sent from content.js
+    messageManager.addMessageListener("Content:ContextMenu", this);
+  },
+
+  // Content forwarding the contextmenu command
+  onContentContextMenu: function onContentContextMenu(aMessage) {
+    let contextInfo = { name: aMessage.name,
+                        json: aMessage.json,
+                        target: aMessage.target };
+    // Touch input selection handling
+    if (!InputSourceHelper.isPrecise) {
+      if (SelectionHelperUI.isActive()) {
+        // Selection handler is active.
+        if (aMessage.json.types.indexOf("selected-text") != -1) {
+          // long tap on existing selection. The incoming message has the
+          // string data, so reset the selection handler and invoke the
+          // context menu.
+          SelectionHelperUI.closeEditSession();
+        } else {
+          // Weird, context menu request with no selected text and
+          // SelectionHelperUI is active? Might be a bug, warn. Fall
+          // through anyway, the context menu handler will look in the
+          // incoming message for content types it knows how to handle.
+          Util.dumpLn("long tap on empty selection with SelectionHelperUI active?"); 
+          SelectionHelperUI.closeEditSession();
+        }
+      } else if (SelectionHelperUI.canHandle(aMessage)) {
+        SelectionHelperUI.openEditSession(aMessage);
+        return;
+      }
+    }
+
+    // Check to see if we have context menu item(s) that apply to what
+    // was clicked on.
+    if (ContextMenuUI.showContextMenu(contextInfo)) {
+      let event = document.createEvent("Events");
+      event.initEvent("CancelTouchSequence", true, false);
+      document.dispatchEvent(event);
+    } else {
+      // Send the MozEdgeUIGesture to input.js to
+      // toggle the context ui.
+      let event = document.createEvent("Events");
+      event.initEvent("MozEdgeUIGesture", true, false);
+      window.dispatchEvent(event);
+    }
+  },
+
+  /*
+   * Events
+   */
+
+  handleEvent: function handleEvent(aEvent) {
+    // ignore content events we generate
+    if (this._debugEvents)
+      Util.dumpLn("BrowserTouchHandler:", aEvent.type);
+
+    switch (aEvent.type) {
+      case "PopupChanged":
+      case "CancelTouchSequence":
+        if (!aEvent.detail)
+          ContextMenuUI.reset();
+        break;
+    }
+  },
+
+  receiveMessage: function receiveMessage(aMessage) {
+    if (this._debugEvents) Util.dumpLn("BrowserTouchHandler:", aMessage.name);
+    switch (aMessage.name) {
+      // Content forwarding the contextmenu command
+      case "Content:ContextMenu":
+        this.onContentContextMenu(aMessage);
+        break;
+    }
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/ContextCommands.js
@@ -0,0 +1,301 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+ /*
+  * context menu command handlers
+  */
+
+var ContextCommands = {
+  _picker: null,
+
+  get clipboard() {
+    return Cc["@mozilla.org/widget/clipboardhelper;1"]
+             .getService(Ci.nsIClipboardHelper);
+  },
+
+  get docRef() {
+    return Browser.selectedBrowser.contentWindow.document;
+  },
+
+  /*
+   * Context menu handlers
+   */
+
+  copy: function cc_copy() {
+    let target = ContextMenuUI.popupState.target;
+    if (target.localName == "browser") {
+      if (ContextMenuUI.popupState.string != "undefined") {
+        this.clipboard.copyString(ContextMenuUI.popupState.string,
+                                  this.docRef);
+      } else {
+        let x = ContextMenuUI.popupState.x;
+        let y = ContextMenuUI.popupState.y;
+        let json = {x: x, y: y, command: "copy" };
+        target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+      }
+    } else {
+      target.editor.copy();
+    }
+
+    if (target)
+      target.focus();
+  },
+
+  paste: function cc_paste() {
+    let target = ContextMenuUI.popupState.target;
+    if (target.localName == "browser") {
+      let x = ContextMenuUI.popupState.x;
+      let y = ContextMenuUI.popupState.y;
+      let json = {x: x, y: y, command: "paste" };
+      target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+    } else {
+      target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
+      target.focus();
+    }
+  },
+
+  pasteAndGo: function cc_pasteAndGo() {
+    let target = ContextMenuUI.popupState.target;
+    target.editor.selectAll();
+    target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
+    BrowserUI.goToURI();
+  },
+
+  selectAll: function cc_selectAll() {
+    let target = ContextMenuUI.popupState.target;
+    if (target.localName == "browser") {
+      let x = ContextMenuUI.popupState.x;
+      let y = ContextMenuUI.popupState.y;
+      let json = {x: x, y: y, command: "select-all" };
+      target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+    } else {
+      target.editor.selectAll();
+      target.focus();
+    }
+  },
+
+  openInNewTab: function cc_openInNewTab() {
+    Browser.addTab(ContextMenuUI.popupState.linkURL, false, Browser.selectedTab);
+    ContextUI.peekTabs();
+  },
+
+  saveToWinLibrary: function cc_saveToWinLibrary(aType) {
+    let popupState = ContextMenuUI.popupState;
+    let browser = popupState.target;
+
+    // ContentAreaUtils internalSave relies on various desktop related prefs,
+    // values, and functionality. We want to be more direct by saving the
+    // image to the users Windows Library. If users want to specify the
+    // save location they can use the context menu option 'Save (type) To'.
+
+    let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+                           .getService(Components.interfaces.nsIProperties);
+    let saveLocationPath = dirSvc.get(aType, Components.interfaces.nsIFile);
+
+    let fileInfo = new ContentAreaUtils.FileInfo();
+    ContentAreaUtils.initFileInfo(fileInfo, popupState.mediaURL,
+                                  browser.documentURI.originCharset,
+                                  null, null, null);
+    let filename =
+      ContentAreaUtils.getNormalizedLeafName(fileInfo.fileName, fileInfo.fileExt);
+    saveLocationPath.append(filename);
+
+    // Start the background save process
+    ContentAreaUtils.internalPersist({
+      sourceURI         : fileInfo.uri,
+      sourceDocument    : null,
+      sourceReferrer    : browser.documentURI,
+      targetContentType : popupState.contentType,
+      targetFile        : saveLocationPath,
+      sourceCacheKey    : null,
+      sourcePostData    : null,
+      bypassCache       : false,
+      initiatingWindow  : this.docRef.defaultView
+    });
+  },
+
+  saveVideo: function cc_saveVideo() {
+    this.saveToWinLibrary("Vids");
+  },
+
+  saveVideoTo: function cc_saveVideoTo() {
+    this.saveFileAs(ContextMenuUI.popupState);
+  },
+
+  saveImage: function cc_saveImage() {
+    this.saveToWinLibrary("Pict");
+  },
+
+  saveImageTo: function cc_saveImageTo() {
+    this.saveFileAs(ContextMenuUI.popupState);
+  },
+
+  copyLink: function cc_copyLink() {
+    this.clipboard.copyString(ContextMenuUI.popupState.linkURL,
+                              this.docRef);
+  },
+
+  copyEmail: function cc_copyEmail() {
+    this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
+                              this.docRef);
+  },
+
+  copyPhone: function cc_copyPhone() {
+    this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
+                              this.docRef);
+  },
+
+  copyImageLocation: function cc_copyImageLocation() {
+    this.clipboard.copyString(ContextMenuUI.popupState.mediaURL,
+                              this.docRef);
+  },
+
+  bookmarkLink: function cc_bookmarkLink() {
+    let state = ContextMenuUI.popupState;
+    let uri = Util.makeURI(state.linkURL);
+    let title = state.linkTitle || state.linkURL;
+
+    try {
+      Bookmarks.addForURI(uri, title);
+    } catch (e) {
+      return;
+    }
+
+    let message = Strings.browser.GetStringFromName("alertLinkBookmarked");
+    let toaster = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
+    toaster.showAlertNotification(null, message, "", false, "", null);
+  },
+
+  sendCommand: function cc_playVideo(aCommand) {
+    let browser = ContextMenuUI.popupState.target;
+    browser.messageManager.sendAsyncMessage("Browser:ContextCommand", { command: aCommand });
+  },
+
+  editBookmark: function cc_editBookmark() {
+    let target = ContextMenuUI.popupState.target;
+    target.startEditing();
+  },
+
+  removeBookmark: function cc_removeBookmark() {
+    let target = ContextMenuUI.popupState.target;
+    target.remove();
+  },
+
+  shortcutBookmark: function cc_shortcutBookmark() {
+    let target = ContextMenuUI.popupState.target;
+    Util.createShortcut(target.getAttribute("title"), target.getAttribute("uri"), target.getAttribute("src"), "bookmark");
+  },
+
+  findInPage: function cc_findInPage() {
+    dump('ContextCommand: findInPage');
+    FindHelperUI.show();
+  },
+
+  viewOnDesktop: function cc_viewOnDesktop() {
+    dump('ContextCommand: viewOnDesktop');
+    Appbar.onViewOnDesktop();
+  },
+
+  /*
+   * Utilities
+   */
+
+  /*
+   * isAccessibleDirectory
+   *
+   * Test to see if the directory exists and is writable.
+   */
+  isAccessibleDirectory: function isAccessibleDirectory(aDirectory) {
+    return aDirectory && aDirectory.exists() && aDirectory.isDirectory() &&
+           aDirectory.isWritable();
+  },
+
+  /*
+   * saveFileAs
+   *
+   * Browse for a location and then save a file to the local system using
+   * standard download prefs.
+   *
+   * @param aFileUriString path to file
+   */
+  saveFileAs: function saveFileAs(aPopupState) {
+    let srcUri = null;
+    let mediaURL = aPopupState.mediaURL;
+    try {
+      srcUri = Util.makeURI(mediaURL, null, null);
+    } catch (ex) {
+      Util.dumpLn("could not parse:", mediaURL);
+      return;
+    }
+
+    let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    let windowTitle = Strings.browser.GetStringFromName("browserForSaveLocation");
+
+    picker.init(window, windowTitle, Ci.nsIFilePicker.modeSave);
+
+    // prefered filename
+    let fileName = mediaURL.substring(mediaURL.lastIndexOf("/") + 1);
+    if (fileName.length)
+      picker.defaultString = fileName;
+
+    // prefered file extension
+    let fileExtension = mediaURL.substring(mediaURL.lastIndexOf(".") + 1);
+    if (fileExtension.length)
+      picker.defaultExtension = fileExtension;
+    picker.appendFilters(Ci.nsIFilePicker.filterImages);
+
+    // prefered save location
+    var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+    picker.displayDirectory = dnldMgr.userDownloadsDirectory;
+    try {
+      let lastDir = Services.prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
+      if (this.isAccessibleDirectory(lastDir))
+        picker.displayDirectory = lastDir;
+    }
+    catch (e) { }
+
+    this._picker = picker;
+    this._pickerUrl = mediaURL;
+    this._pickerContentDisp = aPopupState.contentDisposition;
+    this._contentType = aPopupState.contentType;
+    picker.open(ContextCommands);
+  },
+
+  /*
+   * Filepicker callback
+   */
+  done: function done(aSuccess) {
+    let picker = this._picker;
+    this._picker = null;
+
+    if (aSuccess == Ci.nsIFilePicker.returnCancel)
+      return;
+
+    let file = picker.file;
+    if (file == null)
+      return;
+
+    try {
+      if (file.exists())
+        file.remove(false);
+    } catch (e) {}
+
+    ContentAreaUtils.internalSave(this._pickerUrl, null, null,
+                                  this._pickerContentDisp,
+                                  this._contentType, false, "SaveImageTitle",
+                                  new AutoChosen(file, Util.makeURI(this._pickerUrl, null, null)),
+                                  getBrowser().documentURI, this.docRef, true, null);
+
+    var newDir = file.parent.QueryInterface(Ci.nsILocalFile);
+    Services.prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
+  },
+
+};
+
+function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
+  this.file = aFileAutoChosen;
+  this.uri  = aUriAutoChosen;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/LoginManagerChild.js
@@ -0,0 +1,818 @@
+/* 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/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var loginManager = {
+
+    /* ---------- private members ---------- */
+
+
+    get _formFillService() {
+        return this._formFillService =
+                        Cc["@mozilla.org/satchel/form-fill-controller;1"].
+                        getService(Ci.nsIFormFillController);
+    },
+
+    _nsLoginInfo : null, // Constructor for nsILoginInfo implementation
+    _debug    : false, // mirrors signon.debug
+    _remember : true,  // mirrors signon.rememberSignons preference
+
+    init : function () {
+        // Cache references to current |this| in utility objects
+        this._webProgressListener._domEventListener = this._domEventListener;
+        this._webProgressListener._pwmgr = this;
+        this._domEventListener._pwmgr    = this;
+        this._observer._pwmgr            = this;
+
+        // Form submit observer checks forms for new logins and pw changes.
+        Services.obs.addObserver(this._observer, "earlyformsubmit", false);
+
+        // Get constructor for nsILoginInfo
+        this._nsLoginInfo = new Components.Constructor(
+            "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
+
+        // Preferences. Add observer so we get notified of changes.
+        Services.prefs.addObserver("signon.", this._observer, false);
+
+        // Get current preference values.
+        this._debug = Services.prefs.getBoolPref("signon.debug");
+        this._remember = Services.prefs.getBoolPref("signon.rememberSignons");
+
+        // WebProgressListener for getting notification of new doc loads.
+        var progress = Cc["@mozilla.org/docloaderservice;1"].
+                       getService(Ci.nsIWebProgress);
+        progress.addProgressListener(this._webProgressListener,
+                                     Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+    },
+
+
+    /*
+     * log
+     *
+     * Internal function for logging debug messages to the Error Console window
+     */
+    log : function (message) {
+        if (!this._debug)
+            return;
+        dump("PasswordUtils: " + message + "\n");
+        Services.console.logStringMessage("PasswordUtils: " + message);
+    },
+
+
+    /* fillForm
+     *
+     * Fill the form with login information if we can find it.
+     */
+    fillForm: function (form) {
+        this._fillForm(form, true, true, false, null);
+    },
+
+
+    /*
+     * _fillform
+     *
+     * Fill the form with login information if we can find it.
+     * autofillForm denotes if we should fill the form in automatically
+     * ignoreAutocomplete denotes if we should ignore autocomplete=off attributes
+     * foundLogins is an array of nsILoginInfo
+     */
+    _fillForm : function (form, autofillForm, ignoreAutocomplete,
+                         clobberPassword, foundLogins) {
+        // Heuristically determine what the user/pass fields are
+        // We do this before checking to see if logins are stored,
+        // so that the user isn't prompted for a master password
+        // without need.
+        var [usernameField, passwordField, ignored] =
+            this._getFormFields(form, false);
+
+        // Need a valid password field to do anything.
+        if (passwordField == null)
+            return false;
+
+        // If the fields are disabled or read-only, there's nothing to do.
+        if (passwordField.disabled || passwordField.readOnly ||
+            usernameField && (usernameField.disabled ||
+                              usernameField.readOnly)) {
+            this.log("not filling form, login fields disabled");
+            return false;
+        }
+
+        // Discard logins which have username/password values that don't
+        // fit into the fields (as specified by the maxlength attribute).
+        // The user couldn't enter these values anyway, and it helps
+        // with sites that have an extra PIN to be entered (bug 391514)
+        var maxUsernameLen = Number.MAX_VALUE;
+        var maxPasswordLen = Number.MAX_VALUE;
+
+        // If attribute wasn't set, default is -1.
+        if (usernameField && usernameField.maxLength >= 0)
+            maxUsernameLen = usernameField.maxLength;
+        if (passwordField.maxLength >= 0)
+            maxPasswordLen = passwordField.maxLength;
+
+        var logins = foundLogins.filter(function (l) {
+                var fit = (l.username.length <= maxUsernameLen &&
+                           l.password.length <= maxPasswordLen);
+                if (!fit)
+                    this.log("Ignored " + l.username + " login: won't fit");
+
+                return fit;
+            }, this);
+
+
+        // Nothing to do if we have no matching logins available.
+        if (logins.length == 0)
+            return false;
+
+
+        // The reason we didn't end up filling the form, if any.  We include
+        // this in the formInfo object we send with the passwordmgr-found-logins
+        // notification.  See the _notifyFoundLogins docs for possible values.
+        var didntFillReason = null;
+
+        // Attach autocomplete stuff to the username field, if we have
+        // one. This is normally used to select from multiple accounts,
+        // but even with one account we should refill if the user edits.
+        if (usernameField)
+            this._attachToInput(usernameField);
+
+        // Don't clobber an existing password.
+        if (passwordField.value && !clobberPassword) {
+            didntFillReason = "existingPassword";
+            this._notifyFoundLogins(didntFillReason, usernameField,
+                                    passwordField, foundLogins, null);
+            return false;
+        }
+
+        // If the form has an autocomplete=off attribute in play, don't
+        // fill in the login automatically. We check this after attaching
+        // the autocomplete stuff to the username field, so the user can
+        // still manually select a login to be filled in.
+        var isFormDisabled = false;
+        if (!ignoreAutocomplete &&
+            (this._isAutocompleteDisabled(form) ||
+             this._isAutocompleteDisabled(usernameField) ||
+             this._isAutocompleteDisabled(passwordField))) {
+
+            isFormDisabled = true;
+            this.log("form not filled, has autocomplete=off");
+        }
+
+        // Variable such that we reduce code duplication and can be sure we
+        // should be firing notifications if and only if we can fill the form.
+        var selectedLogin = null;
+
+        if (usernameField && usernameField.value) {
+            // If username was specified in the form, only fill in the
+            // password if we find a matching login.
+            var username = usernameField.value.toLowerCase();
+
+            let matchingLogins = logins.filter(function(l)
+                                     l.username.toLowerCase() == username);
+            if (matchingLogins.length) {
+                selectedLogin = matchingLogins[0];
+            } else {
+                didntFillReason = "existingUsername";
+                this.log("Password not filled. None of the stored " +
+                         "logins match the username already present.");
+            }
+        } else if (logins.length == 1) {
+            selectedLogin = logins[0];
+        } else {
+            // We have multiple logins. Handle a special case here, for sites
+            // which have a normal user+pass login *and* a password-only login
+            // (eg, a PIN). Prefer the login that matches the type of the form
+            // (user+pass or pass-only) when there's exactly one that matches.
+            let matchingLogins;
+            if (usernameField)
+                matchingLogins = logins.filter(function(l) l.username);
+            else
+                matchingLogins = logins.filter(function(l) !l.username);
+            if (matchingLogins.length == 1) {
+                selectedLogin = matchingLogins[0];
+            } else {
+                didntFillReason = "multipleLogins";
+                this.log("Multiple logins for form, so not filling any.");
+            }
+        }
+
+        var didFillForm = false;
+        if (selectedLogin && autofillForm && !isFormDisabled) {
+            // Fill the form
+            if (usernameField)
+                usernameField.value = selectedLogin.username;
+            passwordField.value = selectedLogin.password;
+            didFillForm = true;
+        } else if (selectedLogin && !autofillForm) {
+            // For when autofillForm is false, but we still have the information
+            // to fill a form, we notify observers.
+            didntFillReason = "noAutofillForms";
+            Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
+            this.log("autofillForms=false but form can be filled; notified observers");
+        } else if (selectedLogin && isFormDisabled) {
+            // For when autocomplete is off, but we still have the information
+            // to fill a form, we notify observers.
+            didntFillReason = "autocompleteOff";
+            Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
+            this.log("autocomplete=off but form can be filled; notified observers");
+        }
+
+        this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
+                                foundLogins, selectedLogin);
+
+        return didFillForm;
+    },
+
+
+    /*
+     * _getPasswordOrigin
+     *
+     * Get the parts of the URL we want for identification.
+     */
+    _getPasswordOrigin : function (uriString, allowJS) {
+        var realm = "";
+        try {
+            var uri = Services.io.newURI(uriString, null, null);
+
+            if (allowJS && uri.scheme == "javascript")
+                return "javascript:"
+
+            realm = uri.scheme + "://" + uri.host;
+
+            // If the URI explicitly specified a port, only include it when
+            // it's not the default. (We never want "http://foo.com:80")
+            var port = uri.port;
+            if (port != -1) {
+                var handler = Services.io.getProtocolHandler(uri.scheme);
+                if (port != handler.defaultPort)
+                    realm += ":" + port;
+            }
+
+        } catch (e) {
+            // bug 159484 - disallow url types that don't support a hostPort.
+            // (although we handle "javascript:..." as a special case above.)
+            this.log("Couldn't parse origin for " + uriString);
+            realm = null;
+        }
+
+        return realm;
+    },
+
+
+    _getActionOrigin : function (form) {
+        var uriString = form.action;
+
+        // A blank or mission action submits to where it came from.
+        if (uriString == "")
+            uriString = form.baseURI; // ala bug 297761
+
+        return this._getPasswordOrigin(uriString, true);
+    },
+
+
+    /*
+     * _isAutoCompleteDisabled
+     *
+     * Returns true if the page requests autocomplete be disabled for the
+     * specified form input.
+     */
+    _isAutocompleteDisabled :  function (element) {
+        if (element && element.hasAttribute("autocomplete") &&
+            element.getAttribute("autocomplete").toLowerCase() == "off")
+            return true;
+
+        return false;
+    },
+
+
+    /*
+     * _getFormFields
+     *
+     * Returns the username and password fields found in the form.
+     * Can handle complex forms by trying to figure out what the
+     * relevant fields are.
+     *
+     * Returns: [usernameField, newPasswordField, oldPasswordField]
+     *
+     * usernameField may be null.
+     * newPasswordField will always be non-null.
+     * oldPasswordField may be null. If null, newPasswordField is just
+     * "theLoginField". If not null, the form is apparently a
+     * change-password field, with oldPasswordField containing the password
+     * that is being changed.
+     */
+    _getFormFields : function (form, isSubmission) {
+        var usernameField = null;
+
+        // Locate the password field(s) in the form. Up to 3 supported.
+        // If there's no password field, there's nothing for us to do.
+        var pwFields = this._getPasswordFields(form, isSubmission);
+        if (!pwFields)
+            return [null, null, null];
+
+
+        // Locate the username field in the form by searching backwards
+        // from the first passwordfield, assume the first text field is the
+        // username. We might not find a username field if the user is
+        // already logged in to the site.
+        for (var i = pwFields[0].index - 1; i >= 0; i--) {
+            var element = form.elements[i];
+            var fieldType = (element.hasAttribute("type") ?
+                             element.getAttribute("type").toLowerCase() :
+                             element.type);
+            if (fieldType == "text"  ||
+                fieldType == "email" ||
+                fieldType == "url"   ||
+                fieldType == "tel"   ||
+                fieldType == "number") {
+                usernameField = element;
+                break;
+            }
+        }
+
+        if (!usernameField)
+            this.log("(form -- no username field found)");
+
+
+        // If we're not submitting a form (it's a page load), there are no
+        // password field values for us to use for identifying fields. So,
+        // just assume the first password field is the one to be filled in.
+        if (!isSubmission || pwFields.length == 1)
+            return [usernameField, pwFields[0].element, null];
+
+
+        // Try to figure out WTF is in the form based on the password values.
+        var oldPasswordField, newPasswordField;
+        var pw1 = pwFields[0].element.value;
+        var pw2 = pwFields[1].element.value;
+        var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
+
+        if (pwFields.length == 3) {
+            // Look for two identical passwords, that's the new password
+
+            if (pw1 == pw2 && pw2 == pw3) {
+                // All 3 passwords the same? Weird! Treat as if 1 pw field.
+                newPasswordField = pwFields[0].element;
+                oldPasswordField = null;
+            } else if (pw1 == pw2) {
+                newPasswordField = pwFields[0].element;
+                oldPasswordField = pwFields[2].element;
+            } else if (pw2 == pw3) {
+                oldPasswordField = pwFields[0].element;
+                newPasswordField = pwFields[2].element;
+            } else  if (pw1 == pw3) {
+                // A bit odd, but could make sense with the right page layout.
+                newPasswordField = pwFields[0].element;
+                oldPasswordField = pwFields[1].element;
+            } else {
+                // We can't tell which of the 3 passwords should be saved.
+                this.log("(form ignored -- all 3 pw fields differ)");
+                return [null, null, null];
+            }
+        } else { // pwFields.length == 2
+            if (pw1 == pw2) {
+                // Treat as if 1 pw field
+                newPasswordField = pwFields[0].element;
+                oldPasswordField = null;
+            } else {
+                // Just assume that the 2nd password is the new password
+                oldPasswordField = pwFields[0].element;
+                newPasswordField = pwFields[1].element;
+            }
+        }
+
+        return [usernameField, newPasswordField, oldPasswordField];
+    },
+
+
+    /* ---------- Private methods ---------- */
+
+
+    /*
+     * _getPasswordFields
+     *
+     * Returns an array of password field elements for the specified form.
+     * If no pw fields are found, or if more than 3 are found, then null
+     * is returned.
+     *
+     * skipEmptyFields can be set to ignore password fields with no value.
+     */
+    _getPasswordFields : function (form, skipEmptyFields) {
+        // Locate the password fields in the form.
+        var pwFields = [];
+        for (var i = 0; i < form.elements.length; i++) {
+            var element = form.elements[i];
+            if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
+                element.type != "password")
+                continue;
+
+            if (skipEmptyFields && !element.value)
+                continue;
+
+            pwFields[pwFields.length] = {
+                                            index   : i,
+                                            element : element
+                                        };
+        }
+
+        // If too few or too many fields, bail out.
+        if (pwFields.length == 0) {
+            this.log("(form ignored -- no password fields.)");
+            return null;
+        } else if (pwFields.length > 3) {
+            this.log("(form ignored -- too many password fields. [got " +
+                        pwFields.length + "])");
+            return null;
+        }
+
+        return pwFields;
+    },
+
+
+    /**
+     * Notify observers about an attempt to fill a form that resulted in some
+     * saved logins being found for the form.
+     *
+     * This does not get called if the login manager attempts to fill a form
+     * but does not find any saved logins.  It does, however, get called when
+     * the login manager does find saved logins whether or not it actually
+     * fills the form with one of them.
+     *
+     * @param didntFillReason {String}
+     *        the reason the login manager didn't fill the form, if any;
+     *        if the value of this parameter is null, then the form was filled;
+     *        otherwise, this parameter will be one of these values:
+     *          existingUsername: the username field already contains a username
+     *                            that doesn't match any stored usernames
+     *          existingPassword: the password field already contains a password
+     *          autocompleteOff:  autocomplete has been disabled for the form
+     *                            or its username or password fields
+     *          multipleLogins:   we have multiple logins for the form
+     *          noAutofillForms:  the autofillForms pref is set to false
+     *
+     * @param usernameField   {HTMLInputElement}
+     *        the username field detected by the login manager, if any;
+     *        otherwise null
+     *
+     * @param passwordField   {HTMLInputElement}
+     *        the password field detected by the login manager
+     *
+     * @param foundLogins     {Array}
+     *        an array of nsILoginInfos that can be used to fill the form
+     *
+     * @param selectedLogin   {nsILoginInfo}
+     *        the nsILoginInfo that was/would be used to fill the form, if any;
+     *        otherwise null; whether or not it was actually used depends on
+     *        the value of the didntFillReason parameter
+     */
+    _notifyFoundLogins : function (didntFillReason, usernameField,
+                                   passwordField, foundLogins, selectedLogin) {
+        // We need .setProperty(), which is a method on the original
+        // nsIWritablePropertyBag. Strangley enough, nsIWritablePropertyBag2
+        // doesn't inherit from that, so the additional QI is needed.
+        let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
+                       createInstance(Ci.nsIWritablePropertyBag2).
+                       QueryInterface(Ci.nsIWritablePropertyBag);
+
+        formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
+        formInfo.setPropertyAsInterface("usernameField", usernameField);
+        formInfo.setPropertyAsInterface("passwordField", passwordField);
+        formInfo.setProperty("foundLogins", foundLogins.concat());
+        formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
+
+        Services.obs.notifyObservers(formInfo, "passwordmgr-found-logins", null);
+    },
+
+    /*
+     * _attachToInput
+     *
+     * Hooks up autocomplete support to a username field, to allow
+     * a user editing the field to select an existing login and have
+     * the password field filled in.
+     */
+    _attachToInput : function (element) {
+        this.log("attaching autocomplete stuff");
+        element.addEventListener("blur",
+                                this._domEventListener, false);
+        element.addEventListener("DOMAutoComplete",
+                                this._domEventListener, false);
+        this._formFillService.markAsLoginManagerField(element);
+    },
+
+    /*
+     * _fillDocument
+     *
+     * Called when a page has loaded. For each form in the document,
+     * we ask the parent process to see if it can be filled with a stored login
+     * and fill them in with the results
+     */
+    _fillDocument : function (doc) {
+        var forms = doc.forms;
+        if (!forms || forms.length == 0)
+            return;
+
+        this.log("_fillDocument processing " + forms.length +
+                 " forms on " + doc.documentURI);
+
+        var autofillForm = !PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) &&
+                           Services.prefs.getBoolPref("signon.autofillForms");
+
+        // actionOrigins is a list of each form's action origins for this
+        // document. The parent process needs this to find the passwords
+        // for each action origin.
+        var actionOrigins = [];
+
+        for (var i = 0; i < forms.length; i++) {
+            var form = forms[i];
+            let [, passwordField, ] = this._getFormFields(form, false);
+            if (passwordField) {
+                var actionOrigin = this._getActionOrigin(form);
+                actionOrigins.push(actionOrigin);
+            }
+        } // foreach form
+
+        if (!actionOrigins.length)
+            return;
+
+        var formOrigin = this._getPasswordOrigin(doc.documentURI);
+        var foundLogins = this._getPasswords(actionOrigins, formOrigin);
+
+        for (var i = 0; i < forms.length; i++) {
+            var form = forms[i];
+            var actionOrigin = this._getActionOrigin(form);
+            if (foundLogins[actionOrigin]) {
+                this.log("_fillDocument processing form[" + i + "]");
+                this._fillForm(form, autofillForm, false, false,
+                               foundLogins[actionOrigin]);
+            }
+        } // foreach form
+    },
+
+    /*
+     * _getPasswords
+     *
+     * Retrieve passwords from parent process and prepare logins to be passed to
+     * _fillForm.  Returns map from action origins to passwords.
+     */
+    _getPasswords: function(actionOrigins, formOrigin) {
+        // foundLogins will be a map from action origins to passwords.
+        var message = sendSyncMessage("PasswordMgr:GetPasswords", {
+            actionOrigins: actionOrigins,
+            formOrigin: formOrigin
+        })[0];
+
+        // XXX need to somehow respond to the UI being busy
+        // not needed for Fennec yet
+
+        var foundLogins = message.foundLogins;
+
+        // Each password will be a JSON-unserialized object, but they need to be
+        // nsILoginInfo's.
+        for (var key in foundLogins) {
+            var logins = foundLogins[key];
+            for (var i = 0; i < logins.length; i++) {
+                var obj = logins[i];
+                logins[i] = new this._nsLoginInfo();
+                logins[i].init(obj.hostname, obj.formSubmitURL, obj.httpRealm,
+                               obj.username, obj.password,
+                               obj.usernameField, obj.passwordField);
+            }
+        }
+
+        return foundLogins;
+    },
+
+    /*
+     * _onFormSubmit
+     *
+     * Called by the our observer when notified of a form submission.
+     * [Note that this happens before any DOM onsubmit handlers are invoked.]
+     * Looks for a password change in the submitted form, so we can update
+     * our stored password.
+     */
+    _onFormSubmit : function (form) {
+        var doc = form.ownerDocument;
+        var win = doc.defaultView;
+
+        if (PrivateBrowsingUtils.isWindowPrivate(win)) {
+            // We won't do anything in private browsing mode anyway,
+            // so there's no need to perform further checks.
+            this.log("(form submission ignored in private browsing mode)");
+            return;
+        }
+
+        // If password saving is disabled (globally or for host), bail out now.
+        if (!this._remember)
+            return;
+
+        var hostname      = this._getPasswordOrigin(doc.documentURI);
+        var formSubmitURL = this._getActionOrigin(form);
+
+
+        // Get the appropriate fields from the form.
+        var [usernameField, newPasswordField, oldPasswordField] =
+            this._getFormFields(form, true);
+
+        // Need at least 1 valid password field to do anything.
+        if (newPasswordField == null)
+            return;
+
+        // Check for autocomplete=off attribute. We don't use it to prevent
+        // autofilling (for existing logins), but won't save logins when it's
+        // present.
+        // XXX spin out a bug that we don't update timeLastUsed in this case?
+        if (this._isAutocompleteDisabled(form) ||
+            this._isAutocompleteDisabled(usernameField) ||
+            this._isAutocompleteDisabled(newPasswordField) ||
+            this._isAutocompleteDisabled(oldPasswordField)) {
+                this.log("(form submission ignored -- autocomplete=off found)");
+                return;
+        }
+
+        sendSyncMessage("PasswordMgr:FormSubmitted", {
+            hostname: hostname,
+            formSubmitURL: formSubmitURL,
+            usernameField: usernameField ? usernameField.name : "",
+            usernameValue: usernameField ? usernameField.value : "",
+            passwordField: newPasswordField.name,
+            passwordValue: newPasswordField.value,
+            hasOldPasswordField: !!oldPasswordField
+        });
+    },
+
+    /* ---------- Utility objects ---------- */
+
+    /*
+     * _observer object
+     *
+     * Internal utility object, implements the nsIObserver interface.
+     * Used to receive notification for: form submission, preference changes.
+     */
+    _observer : {
+        _pwmgr : null,
+
+        QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+                                                Ci.nsIFormSubmitObserver,
+                                                Ci.nsISupportsWeakReference]),
+
+
+        // nsFormSubmitObserver
+        notify : function (formElement, aWindow, actionURI) {
+            // Counterintuitively, form submit observers fire for content that 
+            // may not be the content in this context.
+            if (aWindow.top != content)
+                return true;
+
+            this._pwmgr.log("observer notified for form submission.");
+
+            // We're invoked before the content's |onsubmit| handlers, so we
+            // can grab form data before it might be modified (see bug 257781).
+
+            try {
+                this._pwmgr._onFormSubmit(formElement);
+            } catch (e) {
+                this._pwmgr.log("Caught error in onFormSubmit: " + e);
+            }
+
+            return true; // Always return true, or form submit will be canceled.
+        },
+
+        observe : function (aSubject, aTopic, aData) {
+          this._pwmgr._debug    = Services.prefs.getBoolPref("signon.debug");
+          this._pwmgr._remember = Services.prefs.getBoolPref("signon.rememberSignons");
+        }
+    },
+
+
+    /*
+     * _webProgressListener object
+     *
+     * Internal utility object, implements nsIWebProgressListener interface.
+     * This is attached to the document loader service, so we get
+     * notifications about all page loads.
+     */
+    _webProgressListener : {
+        _pwmgr : null,
+        _domEventListener : null,
+
+        QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                                Ci.nsISupportsWeakReference]),
+
+
+        onStateChange : function (aWebProgress, aRequest,
+                                  aStateFlags,  aStatus) {
+
+            // STATE_START is too early, doc is still the old page.
+            if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_TRANSFERRING))
+                return;
+
+            if (!this._pwmgr._remember)
+                return;
+
+            var domWin = aWebProgress.DOMWindow;
+            var domDoc = domWin.document;
+
+            // Only process things which might have HTML forms.
+            if (!(domDoc instanceof Ci.nsIDOMHTMLDocument))
+                return;
+            if (this._pwmgr._debug) {
+                let requestName = "(null)";
+                if (aRequest) {
+                    try {
+                        requestName = aRequest.name;
+                    } catch (ex if ex.result == Components.results.NS_ERROR_NOT_IMPLEMENTED) {
+                        // do nothing - leave requestName = "(null)"
+                    }
+                }
+                this._pwmgr.log("onStateChange accepted: req = " + requestName +
+                                ", flags = 0x" + aStateFlags.toString(16));
+            }
+
+            // Fastback doesn't fire DOMContentLoaded, so process forms now.
+            if (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) {
+                this._pwmgr.log("onStateChange: restoring document");
+                return this._pwmgr._fillDocument(domDoc);
+            }
+
+            // Add event listener to process page when DOM is complete.
+            domDoc.addEventListener("DOMContentLoaded",
+                                    this._domEventListener, false);
+            return;
+        },
+
+        // stubs for the nsIWebProgressListener interfaces which we don't use.
+        onProgressChange : function() { throw "Unexpected onProgressChange"; },
+        onLocationChange : function() { throw "Unexpected onLocationChange"; },
+        onStatusChange   : function() { throw "Unexpected onStatusChange";   },
+        onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
+    },
+
+    /*
+     * _domEventListener object
+     *
+     * Internal utility object, implements nsIDOMEventListener
+     * Used to catch certain DOM events needed to properly implement form fill.
+     */
+    _domEventListener : {
+        _pwmgr : null,
+
+        QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
+                                                Ci.nsISupportsWeakReference]),
+
+
+        handleEvent : function (event) {
+            if (!event.isTrusted)
+                return;
+
+            this._pwmgr.log("domEventListener: got event " + event.type);
+
+            switch (event.type) {
+                case "DOMAutoComplete":
+                case "blur":
+                    var acInputField = event.target;
+                    var acForm = acInputField.form;
+
+                    // If the username is blank, bail out now -- we don't want
+                    // _fillForm() to try filling in a login without a username
+                    // to filter on (bug 471906).
+                    if (!acInputField.value)
+                        return;
+
+                    // Make sure the username field _fillForm will use is the
+                    // same field as the autocomplete was activated on. If
+                    // not, the DOM has been altered and we'll just give up.
+                    var [usernameField, passwordField, ignored] =
+                        this._pwmgr._getFormFields(acForm, false);
+                    if (usernameField == acInputField && passwordField) {
+                        var actionOrigin = this._pwmgr._getActionOrigin(acForm);
+                        var formOrigin = this._pwmgr._getPasswordOrigin(acForm.ownerDocument.documentURI);
+                        var foundLogins = this._pwmgr._getPasswords([actionOrigin], formOrigin);
+                        this._pwmgr._fillForm(acForm, true, true, true, foundLogins[actionOrigin]);
+                    } else {
+                        this._pwmgr.log("Oops, form changed before AC invoked");
+                    }
+                    return;
+
+                case "DOMContentLoaded":
+                    // Only process when we need to
+                    event.currentTarget.removeEventListener(event.type, this);
+                    if (this._pwmgr._remember && event.target instanceof Ci.nsIDOMHTMLDocument)
+                        this._pwmgr._fillDocument(event.target);
+                    break;
+
+                default:
+                    this._pwmgr.log("Oops! This event unexpected.");
+                    return;
+            }
+        }
+    }
+};
+
+loginManager.init();
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/PageActions.js
@@ -0,0 +1,180 @@
+/* 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/. */
+
+var PageActions = {
+  _handlers: null,
+
+  init: function init() {
+    if (this._handlers)
+      return;
+
+    this._handlers = [];
+    document.getElementById("pageactions-container").addEventListener("click", this, true);
+
+    this.register("pageaction-reset", this.updatePagePermissions, this);
+    this.register("pageaction-password", this.updateForgetPassword, this);
+#ifdef NS_PRINTING
+    this.register("pageaction-saveas", this.updatePageSaveAs, this);
+#endif
+    this.register("pageaction-share", this.updateShare, this);
+
+    CharsetMenu.init();
+  },
+
+  handleEvent: function handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "click":
+        IdentityUI.hide();
+        break;
+    }
+  },
+
+  /**
+   * @param aId id of a pageaction element
+   * @param aCallback function that takes an element and returns true if it should be visible
+   * @param aThisObj (optional) scope object for aCallback
+   */
+  register: function register(aId, aCallback, aThisObj) {
+    this.init();
+    this._handlers.push({id: aId, callback: aCallback, obj: aThisObj});
+  },
+
+  updateSiteMenu: function updateSiteMenu() {
+    this.init();
+    this._handlers.forEach(function(action) {
+      let node = document.getElementById(action.id);
+      if (node)
+        node.hidden = !action.callback.call(action.obj, node);
+    });
+    this._updateAttributes();
+  },
+
+  get _loginManager() {
+    delete this._loginManager;
+    return this._loginManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
+  },
+
+  // Permissions we track in Page Actions
+  _permissions: ["popup", "offline-app", "geolocation", "desktop-notification", "openWebappsManage"],
+
+  _forEachPermissions: function _forEachPermissions(aHost, aCallback) {
+    let pm = Services.perms;
+    for (let i = 0; i < this._permissions.length; i++) {
+      let type = this._permissions[i];
+      if (!pm.testPermission(aHost, type))
+        continue;
+
+      let perms = pm.enumerator;
+      while (perms.hasMoreElements()) {
+        let permission = perms.getNext().QueryInterface(Ci.nsIPermission);
+        if (permission.host == aHost.asciiHost && permission.type == type)
+          aCallback(type);
+      }
+    }
+  },
+
+  updatePagePermissions: function updatePagePermissions(aNode) {
+    let host = Browser.selectedBrowser.currentURI;
+    let permissions = [];
+
+    this._forEachPermissions(host, function(aType) {
+      permissions.push("pageactions." + aType);
+    });
+
+    if (!this._loginManager.getLoginSavingEnabled(host.prePath)) {
+      // If rememberSignons is false, then getLoginSavingEnabled returns false
+      // for all pages, so we should just ignore it (Bug 601163).
+      if (Services.prefs.getBoolPref("signon.rememberSignons"))
+        permissions.push("pageactions.password");
+    }
+
+    let descriptions = permissions.map(function(s) Strings.browser.GetStringFromName(s));
+    aNode.setAttribute("description", descriptions.join(", "));
+
+    return (permissions.length > 0);
+  },
+
+  updateForgetPassword: function updateForgetPassword(aNode) {
+    let host = Browser.selectedBrowser.currentURI;
+    let logins = this._loginManager.findLogins({}, host.prePath, "", "");
+
+    return logins.some(function(login) login.hostname == host.prePath);
+  },
+
+  forgetPassword: function forgetPassword(aEvent) {
+    let host = Browser.selectedBrowser.currentURI;
+    let lm = this._loginManager;
+
+    lm.findLogins({}, host.prePath, "", "").forEach(function(login) {
+      if (login.hostname == host.prePath)
+        lm.removeLogin(login);
+    });
+
+    this.hideItem(aEvent.target);
+    aEvent.stopPropagation(); // Don't hide the site menu.
+  },
+
+  clearPagePermissions: function clearPagePermissions(aEvent) {
+    let pm = Services.perms;
+    let host = Browser.selectedBrowser.currentURI;
+    this._forEachPermissions(host, function(aType) {
+      pm.remove(host.asciiHost, aType);
+
+      // reset the 'remember' counter for permissions that support it
+      if (["geolocation", "desktop-notification"].indexOf(aType) != -1)
+        Services.contentPrefs.setPref(host.asciiHost, aType + ".request.remember", 0);
+    });
+
+    let lm = this._loginManager;
+    if (!lm.getLoginSavingEnabled(host.prePath))
+      lm.setLoginSavingEnabled(host.prePath, true);
+
+    this.hideItem(aEvent.target);
+    aEvent.stopPropagation(); // Don't hide the site menu.
+  },
+
+  pinSite : function pinCurrentSite() {
+    if (Browser.selectedBrowser.currentURI.spec.length == 0) {
+      return;
+    }
+    Browser.pinSite();
+  },
+
+  unpinSite : function unpinCurrentSite() {
+    if (Browser.selectedBrowser.currentURI.spec.length == 0) {
+      return;
+    }
+    Browser.unpinSite();
+  },
+
+  updatePageSaveAs: function updatePageSaveAs(aNode) {
+    // Check for local XUL content
+    let contentWindow = Browser.selectedBrowser.contentWindow;
+    return !(contentWindow && contentWindow.document instanceof XULDocument);
+  },
+
+  updateShare: function updateShare(aNode) {
+    return Util.isShareableScheme(Browser.selectedBrowser.currentURI.scheme);
+  },
+
+  hideItem: function hideItem(aNode) {
+    aNode.hidden = true;
+    this._updateAttributes();
+  },
+
+  _updateAttributes: function _updateAttributes() {
+    let container = document.getElementById("pageactions-container");
+    let visibleNodes = container.querySelectorAll("pageaction:not([hidden=true])");
+    let visibleCount = visibleNodes.length;
+    if (visibleCount == 0)
+      return;
+
+    for (let i = 0; i < visibleCount; i++)
+      visibleNodes[i].classList.remove("odd-last-child");
+
+    visibleNodes[visibleCount - 1].classList.add("last-child");
+    if (visibleCount % 2)
+      visibleNodes[visibleCount - 1].classList.add("odd");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/RemoteTabs.js
@@ -0,0 +1,144 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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';
+Components.utils.import("resource://services-sync/main.js");
+
+/**
+ * Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
+ * fills it with the user's synced tabs.
+ *
+ * @param    aSet         Control implementing nsIDOMXULSelectControlElement.
+ * @param    aSetUIAccess The UI element that should be hidden when Sync is
+ *                          disabled. Must sanely support 'hidden' attribute.
+ *                          You may only have one UI access point at this time.
+ */
+function RemoteTabsView(aSet, aSetUIAccess) {
+  this._set = aSet;
+  this._set.controller = this;
+  this._uiAccessElement = aSetUIAccess;
+
+  // Sync uses special voodoo observers.
+  // If you want to change this code, talk to the fx-si team
+  Weave.Svc.Obs.add("weave:service:setup-complete", this);
+  Weave.Svc.Obs.add("weave:service:sync:finish", this);
+  Weave.Svc.Obs.add("weave:service:start-over", this);
+  if (this.isSyncEnabled() ) {
+    this.populateTabs();
+    this.populateGrid();
+    this.setUIAccessVisible(true);
+  }
+  else {
+    this.setUIAccessVisible(false);
+  }
+}
+
+RemoteTabsView.prototype = {
+  _set: null,
+  _uiAccessElement: null,
+
+  handleItemClick: function tabview_handleItemClick(aItem) {
+    let url = aItem.getAttribute("value");
+    BrowserUI.goToURI(url);
+  },
+
+  observe: function(subject, topic, data) {
+    switch (topic) {
+      case "weave:service:setup-complete":
+        this.populateTabs();
+        this.setUIAccessVisible(true);
+        break;
+      case "weave:service:sync:finish":
+        this.populateGrid();
+        break;
+      case "weave:service:start-over":
+        this.setUIAccessVisible(false);
+        break;
+    }
+  },
+
+  setUIAccessVisible: function setUIAccessVisible(aVisible) {
+    this._uiAccessElement.hidden = !aVisible;
+  },
+
+  populateGrid: function populateGrid() {
+
+    let tabsEngine = Weave.Service.engineManager.get("tabs");
+    let list = this._set;
+    let seenURLs = new Set();
+
+    for (let [guid, client] in Iterator(tabsEngine.getAllClients())) {
+      client.tabs.forEach(function({title, urlHistory, icon}) {
+        let url = urlHistory[0];
+        if (tabsEngine.locallyOpenTabMatchesURL(url) || seenURLs.has(url)) {
+          return;
+        }
+        seenURLs.add(url);
+
+        // If we wish to group tabs by client, we should be looking for records
+        //  of {type:client, clientName, class:{mobile, desktop}} and will
+        //  need to readd logic to reset seenURLs for each client.
+
+        let item = this._set.appendItem((title || url), url);
+        item.setAttribute("iconURI", Weave.Utils.getIcon(icon));
+
+      }, this);
+    }
+  },
+
+  populateTabs: function populateTabs() {
+    Weave.Service.scheduler.scheduleNextSync(0);
+  },
+
+  destruct: function destruct() {
+    Weave.Svc.Obs.remove("weave:service:setup-complete", this);
+    Weave.Svc.Obs.remove("weave:engine:sync:finish", this);
+    Weave.Svc.Obs.remove("weave:service:logout:start-over", this);
+  },
+
+  isSyncEnabled: function isSyncEnabled() {
+    return (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
+  }
+
+};
+
+let RemoteTabsStartView = {
+  _view: null,
+  get _grid() { return document.getElementById("start-remotetabs-grid"); },
+
+  init: function init() {
+    let vbox = document.getElementById("start-remotetabs");
+    this._view = new RemoteTabsView(this._grid, vbox);
+  },
+
+  uninit: function uninit() {
+    this._view.destruct();
+  },
+
+  show: function show() {
+    this._grid.arrangeItems();
+  }
+};
+
+let RemoteTabsPanelView = {
+  _view: null,
+
+  get _grid() { return document.getElementById("remotetabs-list"); },
+  get visible() { return PanelUI.isPaneVisible("remotetabs-container"); },
+
+  init: function init() {
+    //decks are fragile, don't hide the tab panel(bad things happen), hide link.
+    let menuEntry = document.getElementById("menuitem-remotetabs");
+    this._view = new RemoteTabsView(this._grid, menuEntry);
+  },
+
+  show: function show() {
+    this._grid.arrangeItems();
+  },
+
+  uninit: function uninit() {
+    this._view.destruct();
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/TopSites.js
@@ -0,0 +1,201 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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';
+ let prefs = Components.classes["@mozilla.org/preferences-service;1"].
+      getService(Components.interfaces.nsIPrefBranch);
+
+// singleton to provide data-level functionality to the views
+let TopSites = {
+  pinSite: function(aId, aSlotIndex) {
+    Util.dumpLn("TopSites.pinSite: " + aId + ", (TODO)");
+    // FIXME: implementation needed
+    return true; // operation was successful
+  },
+  unpinSite: function(aId) {
+    Util.dumpLn("TopSites.unpinSite: " + aId + ", (TODO)");
+    // FIXME: implementation needed
+    return true; // operation was successful
+  },
+  hideSite: function(aId) {
+    Util.dumpLn("TopSites.hideSite: " + aId + ", (TODO)");
+    // FIXME: implementation needed
+    return true; // operation was successful
+  },
+  restoreSite: function(aId) {
+    Util.dumpLn("TopSites.restoreSite: " + aId + ", (TODO)");
+    // FIXME: implementation needed
+    return true; // operation was successful
+  }
+};
+
+function TopSitesView(aGrid, maxSites) {
+  this._set = aGrid;
+  this._set.controller = this;
+  this._topSitesMax = maxSites;
+
+  // handle selectionchange DOM events from the grid/tile group
+  this._set.addEventListener("context-action", this, false);
+}
+
+TopSitesView.prototype = {
+  _set:null,
+  _topSitesMax: null,
+
+  handleItemClick: function tabview_handleItemClick(aItem) {
+    let url = aItem.getAttribute("value");
+    BrowserUI.goToURI(url);
+  },
+
+  doActionOnSelectedTiles: function(aActionName) {
+    let tileGroup = this._set;
+    let selectedTiles = tileGroup.selectedItems;
+
+    switch (aActionName){
+      case "delete":
+        Array.forEach(selectedTiles, function(aNode) {
+          let id = aNode.getAttribute("data-itemid");
+          // add some class to transition element before deletion?
+          if (TopSites.hideSite(id)) {
+            // success
+            aNode.contextActions.delete('delete');
+            aNode.contextActions.add('restore');
+          }
+          // TODO: we'll use some callback/event to remove the item or re-draw the grid
+        });
+        break;
+      case "pin":
+        Array.forEach(selectedTiles, function(aNode) {
+          let id = aNode.getAttribute("data-itemid");
+          if (TopSites.pinSite(id)) {
+            // success
+            aNode.contextActions.delete('pin');
+            aNode.contextActions.add('unpin');
+          }
+          // TODO: we'll use some callback/event to add some class to
+          // indicate element is pinned?
+        });
+        break;
+      case "unpin":
+        Array.forEach(selectedTiles, function(aNode) {
+          let id = aNode.getAttribute("data-itemid");
+          if (TopSites.unpinSite(id)) {
+            // success
+            aNode.contextActions.delete('unpin');
+            aNode.contextActions.add('pin');
+          }
+          // TODO: we'll use some callback/event to add some class to
+          // indicate element is pinned (or just redraw grid)
+        });
+        break;
+      // default: no action
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    switch (aEvent.type){
+      case "context-action":
+        this.doActionOnSelectedTiles(aEvent.action);
+        break;
+    }
+  },
+
+  populateGrid: function populateGrid() {
+    let query = gHistSvc.getNewQuery();
+    let options = gHistSvc.getNewQueryOptions();
+    options.excludeQueries = true;
+    options.queryType = options.QUERY_TYPE_HISTORY;
+    options.maxResults = this._topSitesMax;
+    options.resultType = options.RESULTS_AS_URI;
+    options.sortingMode = options.SORT_BY_FRECENCY_DESCENDING;
+
+    let result = gHistSvc.executeQuery(query, options);
+    let rootNode = result.root;
+    rootNode.containerOpen = true;
+    let childCount = rootNode.childCount;
+
+    // use this property as the data-itemid attribute on the tiles
+    // which identifies the site
+    let identifier = 'uri';
+
+    function isPinned(aNode) {
+      // placeholder condition,
+      // FIXME: do the actual lookup/check
+      return (aNode.uri.indexOf('google') > -1);
+    }
+
+    for (let i = 0; i < childCount; i++) {
+      let node = rootNode.getChild(i);
+      let uri = node.uri;
+      let title = node.title || uri;
+
+      let supportedActions = ['delete'];
+      // placeholder condition - check field/property for this site
+      if (isPinned(node)) {
+        supportedActions.push('unpin');
+      } else {
+        supportedActions.push('pin');
+      }
+      let item = this._set.appendItem(title, uri);
+      item.setAttribute("iconURI", node.icon);
+      item.setAttribute("data-itemid", node[identifier]);
+      // here is where we could add verbs based on pinned etc. state
+      item.setAttribute("data-contextactions", supportedActions.join(','));
+    }
+    rootNode.containerOpen = false;
+  },
+
+  isFirstRun: function isFirstRun() {
+    return prefs.getBoolPref("browser.firstrun.show.localepicker");
+  },
+
+  destruct: function destruct() {
+    // remove the observers here
+  }
+
+};
+
+let TopSitesStartView = {
+  _view: null,
+  get _grid() { return document.getElementById("start-topsites-grid"); },
+
+  init: function init() {
+    this._view = new TopSitesView(this._grid, 9);
+    if (this._view.isFirstRun()) {
+      let topsitesVbox = document.getElementById("start-topsites");
+      topsitesVbox.setAttribute("hidden", "true");
+    }
+    this._view.populateGrid();
+  },
+
+  uninit: function uninit() {
+    this._view.destruct();
+  },
+
+  show: function show() {
+    this._grid.arrangeItems(3, 3);
+  },
+};
+
+let TopSitesSnappedView = {
+  get _grid() { return document.getElementById("snapped-topsite-grid"); },
+
+  show: function show() {
+    this._grid.arrangeItems(1, 9);
+  },
+
+  init: function() {
+    this._view = new TopSitesView(this._grid, 9);
+    if (this._view.isFirstRun()) {
+      let topsitesVbox = document.getElementById("snapped-topsites");
+      topsitesVbox.setAttribute("hidden", "true");
+    }
+    this._view.populateGrid();
+  },
+
+  uninit: function uninit() {
+    this._view.destruct();
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/Util.js
@@ -0,0 +1,412 @@
+/* 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/. */
+
+let Util = {
+  /*
+   * General purpose utilities
+   */
+
+  getWindowUtils: function getWindowUtils(aWindow) {
+    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+  },
+
+  // Recursively find all documents, including root document.
+  getAllDocuments: function getAllDocuments(doc, resultSoFar) {
+    resultSoFar = resultSoFar || [doc];
+    if (!doc.defaultView)
+      return resultSoFar;
+    let frames = doc.defaultView.frames;
+    if (!frames)
+      return resultSoFar;
+
+    let i;
+    let currentDoc;
+    for (i = 0; i < frames.length; i++) {
+      currentDoc = frames[i].document;
+      resultSoFar.push(currentDoc);
+      this.getAllDocuments(currentDoc, resultSoFar);
+    }
+
+    return resultSoFar;
+  },
+
+  // Put the Mozilla networking code into a state that will kick the
+  // auto-connection process.
+  forceOnline: function forceOnline() {
+    Services.io.offline = false;
+  },
+
+  /*
+   * Timing utilties
+   */
+
+  // Executes aFunc after other events have been processed.
+  executeSoon: function executeSoon(aFunc) {
+    Services.tm.mainThread.dispatch({
+      run: function() {
+        aFunc();
+      }
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  /*
+   * Console printing utilities
+   */
+
+  dumpf: function dumpf(str) {
+    let args = arguments;
+    let i = 1;
+    dump(str.replace(/%s/g, function() {
+      if (i >= args.length) {
+        throw "dumps received too many placeholders and not enough arguments";
+      }
+      return args[i++].toString();
+    }));
+  },
+
+  // Like dump, but each arg is handled and there's an automatic newline
+  dumpLn: function dumpLn() {
+    for (let i = 0; i < arguments.length; i++)
+      dump(arguments[i] + " ");
+    dump("\n");
+  },
+
+  dumpElement: function dumpElement(aElement) {
+    this.dumpLn(aElement.id);
+  },
+
+  dumpElementTree: function dumpElementTree(aElement) {
+    let node = aElement;
+    while (node) {
+      this.dumpLn("node:", node, "id:", node.id, "class:", node.classList);
+      node = node.parentNode;
+    }
+  },
+
+  /*
+   * Element utilities
+   */
+
+  highlightElement: function highlightElement(aElement) {
+    if (aElement == null) {
+      this.dumpLn("aElement is null");
+      return;
+    }
+    aElement.style.border = "2px solid red";
+  },
+
+  getHrefForElement: function getHrefForElement(target) {
+    let link = null;
+    while (target) {
+      if (target instanceof Ci.nsIDOMHTMLAnchorElement || 
+          target instanceof Ci.nsIDOMHTMLAreaElement ||
+          target instanceof Ci.nsIDOMHTMLLinkElement) {
+          if (target.hasAttribute("href"))
+            link = target;
+      }
+      target = target.parentNode;
+    }
+
+    if (link && link.hasAttribute("href"))
+      return link.href;
+    else
+      return null;
+  },
+
+  /*
+   * Rect and nsIDOMRect utilities
+   */
+
+  pointWithinRect: function pointWithinRect(aX, aY, aRect) {
+    return (aRect.left < aX && aRect.top < aY &&
+            aRect.right > aX && aRect.bottom > aY);
+  },
+
+  pointWithinDOMRect: function pointWithinDOMRect(aX, aY, aRect) {
+    if (!aRect.width || !aRect.height)
+      return false;
+    return this.pointWithinRect(aX, aY, aRect);
+  },
+
+  isEmptyDOMRect: function isEmptyDOMRect(aRect) {
+    if ((aRect.bottom - aRect.top) <= 0 &&
+        (aRect.right - aRect.left) <= 0)
+      return true;
+    return false;
+  },
+
+  // Dumps the details of a dom rect to the console
+  dumpDOMRect: function dumpDOMRect(aMsg, aRect) {
+    try {
+      Util.dumpLn(aMsg,
+                  "left:" + Math.round(aRect.left) + ",",
+                  "top:" + Math.round(aRect.top) + ",",
+                  "right:" + Math.round(aRect.right) + ",",
+                  "bottom:" + Math.round(aRect.bottom) + ",",
+                  "width:" + Math.round(aRect.right - aRect.left) + ",",
+                  "height:" + Math.round(aRect.bottom - aRect.top) );
+    } catch (ex) {
+      Util.dumpLn("dumpDOMRect:", ex.message);
+    }
+  },
+
+  /*
+   * URIs and schemes
+   */
+
+  makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
+    return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
+  },
+
+  makeURLAbsolute: function makeURLAbsolute(base, url) {
+    // Note:  makeURI() will throw if url is not a valid URI
+    return this.makeURI(url, null, this.makeURI(base)).spec;
+  },
+
+  isLocalScheme: function isLocalScheme(aURL) {
+    return ((aURL.indexOf("about:") == 0 &&
+             aURL != "about:blank" &&
+             aURL != "about:empty" &&
+             aURL != "about:start") ||
+            aURL.indexOf("chrome:") == 0);
+  },
+
+  isOpenableScheme: function isShareableScheme(aProtocol) {
+    let dontOpen = /^(mailto|javascript|news|snews)$/;
+    return (aProtocol && !dontOpen.test(aProtocol));
+  },
+
+  isShareableScheme: function isShareableScheme(aProtocol) {
+    let dontShare = /^(chrome|about|file|javascript|resource)$/;
+    return (aProtocol && !dontShare.test(aProtocol));
+  },
+
+  // Don't display anything in the urlbar for these special URIs.
+  isURLEmpty: function isURLEmpty(aURL) {
+    return (!aURL ||
+            aURL == "about:blank" ||
+            aURL == "about:empty" ||
+            aURL == "about:home" ||
+            aURL == "about:start");
+  },
+
+  // Don't remember these pages in the session store.
+  isURLMemorable: function isURLMemorable(aURL) {
+    return !(aURL == "about:blank" ||
+             aURL == "about:empty" ||
+             aURL == "about:start");
+  },
+
+  /*
+   * Math utilities
+   */
+
+  clamp: function(num, min, max) {
+    return Math.max(min, Math.min(max, num));
+  },
+
+  /*
+   * Screen and layout utilities
+   */
+
+  get displayDPI() {
+    delete this.displayDPI;
+    return this.displayDPI = this.getWindowUtils(window).displayDPI;
+  },
+
+  isPortrait: function isPortrait() {
+    return (window.innerWidth <= window.innerHeight);
+  },
+
+  LOCALE_DIR_RTL: -1,
+  LOCALE_DIR_LTR: 1,
+  get localeDir() {
+    // determine browser dir first to know which direction to snap to
+    let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+    return chromeReg.isLocaleRTL("global") ? this.LOCALE_DIR_RTL : this.LOCALE_DIR_LTR;
+  },
+
+  /*
+   * Process utilities
+   */
+
+  isParentProcess: function isInParentProcess() {
+    let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+    return (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
+  },
+
+  /*
+   * Event utilities
+   */
+
+  modifierMaskFromEvent: function modifierMaskFromEvent(aEvent) {
+    return (aEvent.altKey   ? Ci.nsIDOMEvent.ALT_MASK     : 0) |
+           (aEvent.ctrlKey  ? Ci.nsIDOMEvent.CONTROL_MASK : 0) |
+           (aEvent.shiftKey ? Ci.nsIDOMEvent.SHIFT_MASK   : 0) |
+           (aEvent.metaKey  ? Ci.nsIDOMEvent.META_MASK    : 0);
+  },
+
+  /*
+   * Download utilities
+   */
+
+  insertDownload: function insertDownload(aSrcUri, aFile) {
+    let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+    let db = dm.DBConnection;
+
+    let stmt = db.createStatement(
+      "INSERT INTO moz_downloads (name, source, target, startTime, endTime, state, referrer) " +
+      "VALUES (:name, :source, :target, :startTime, :endTime, :state, :referrer)"
+    );
+
+    stmt.params.name = aFile.leafName;
+    stmt.params.source = aSrcUri.spec;
+    stmt.params.target = aFile.path;
+    stmt.params.startTime = Date.now() * 1000;
+    stmt.params.endTime = Date.now() * 1000;
+    stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
+    stmt.params.referrer = aSrcUri.spec;
+
+    stmt.execute();
+    stmt.finalize();
+
+    let newItemId = db.lastInsertRowID;
+    let download = dm.getDownload(newItemId);
+    //dm.resumeDownload(download);
+    //Services.obs.notifyObservers(download, "dl-start", null);
+  },
+
+  /*
+   * Local system utilities
+   */
+
+  createShortcut: function Util_createShortcut(aTitle, aURL, aIconURL, aType) {
+    // The background images are 72px, but Android will resize as needed.
+    // Bigger is better than too small.
+    const kIconSize = 72;
+    const kOverlaySize = 32;
+    const kOffset = 20;
+
+    // We have to fallback to something
+    aTitle = aTitle || aURL;
+
+    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    canvas.setAttribute("style", "display: none");
+
+    function _createShortcut() {
+      let icon = canvas.toDataURL("image/png", "");
+      canvas = null;
+      try {
+        let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
+        shell.createShortcut(aTitle, aURL, icon, aType);
+      } catch(e) {
+        Cu.reportError(e);
+      }
+    }
+
+    // Load the main background image first
+    let image = new Image();
+    image.onload = function() {
+      canvas.width = canvas.height = kIconSize;
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(image, 0, 0, kIconSize, kIconSize);
+
+      // If we have a favicon, lets draw it next
+      if (aIconURL) {
+        let favicon = new Image();
+        favicon.onload = function() {
+          // Center the favicon and overlay it on the background
+          ctx.drawImage(favicon, kOffset, kOffset, kOverlaySize, kOverlaySize);
+          _createShortcut();
+        }
+
+        favicon.onerror = function() {
+          Cu.reportError("CreateShortcut: favicon image load error");
+        }
+
+        favicon.src = aIconURL;
+      } else {
+        _createShortcut();
+      }
+    }
+
+    image.onerror = function() {
+      Cu.reportError("CreateShortcut: background image load error");
+    }
+
+    // Pick the right background
+    image.src = aIconURL ? "chrome://browser/skin/images/homescreen-blank-hdpi.png"
+                         : "chrome://browser/skin/images/homescreen-default-hdpi.png";
+  },
+};
+
+
+/*
+ * Timeout
+ *
+ * Helper class to nsITimer that adds a little more pizazz.  Callback can be an
+ * object with a notify method or a function.
+ */
+Util.Timeout = function(aCallback) {
+  this._callback = aCallback;
+  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  this._type = null;
+};
+
+Util.Timeout.prototype = {
+  // Timer callback. Don't call this manually.
+  notify: function notify() {
+    if (this._type == this._timer.TYPE_ONE_SHOT)
+      this._type = null;
+
+    if (this._callback.notify)
+      this._callback.notify();
+    else
+      this._callback.apply(null);
+  },
+
+  // Helper function for once and interval.
+  _start: function _start(aDelay, aType, aCallback) {
+    if (aCallback)
+      this._callback = aCallback;
+    this.clear();
+    this._timer.initWithCallback(this, aDelay, aType);
+    this._type = aType;
+    return this;
+  },
+
+  // Do the callback once.  Cancels other timeouts on this object.
+  once: function once(aDelay, aCallback) {
+    return this._start(aDelay, this._timer.TYPE_ONE_SHOT, aCallback);
+  },
+
+  // Do the callback every aDelay msecs. Cancels other timeouts on this object.
+  interval: function interval(aDelay, aCallback) {
+    return this._start(aDelay, this._timer.TYPE_REPEATING_SLACK, aCallback);
+  },
+
+  // Clear any pending timeouts.
+  clear: function clear() {
+    if (this.isPending()) {
+      this._timer.cancel();
+      this._type = null;
+    }
+    return this;
+  },
+
+  // If there is a pending timeout, call it and cancel the timeout.
+  flush: function flush() {
+    if (this.isPending()) {
+      this.notify();
+      this.clear();
+    }
+    return this;
+  },
+
+  // Return true if we are waiting for a callback. 
+  isPending: function isPending() {
+    return this._type !== null;
+  }
+};
+
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/WebProgress.js
@@ -0,0 +1,209 @@
+/* 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/. */
+
+// Progress heartbeat timer duration (ms)
+const kHeartbeatDuration = 1000;
+// Start and end progress screen css margins as percentages
+const kProgressMarginStart = 30;
+const kProgressMarginEnd = 70;
+
+const WebProgress = {
+  _progressActive: false,
+
+  init: function init() {
+    messageManager.addMessageListener("Content:StateChange", this);
+    messageManager.addMessageListener("Content:LocationChange", this);
+    messageManager.addMessageListener("Content:SecurityChange", this);
+    Elements.progress.addEventListener("transitionend", this._progressTransEnd, true);
+    return this;
+  },
+
+  receiveMessage: function receiveMessage(aMessage) {
+    let json = aMessage.json;
+    let tab = Browser.getTabForBrowser(aMessage.target);
+
+    switch (aMessage.name) {
+      case "Content:StateChange": {
+        if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+          if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START)
+            this._windowStart(json, tab);
+          else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP)
+            this._windowStop(json, tab);
+        }
+
+        if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+          if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START)
+            this._networkStart(json, tab);
+          else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP)
+            this._networkStop(json, tab);
+        }
+
+        this._progressStep();
+        break;
+      }
+
+      case "Content:LocationChange": {
+        this._locationChange(json, tab);
+        this._progressStep();
+        break;
+      }
+
+      case "Content:SecurityChange": {
+        this._securityChange(json, tab);
+        this._progressStep();
+        break;
+      }
+    }
+  },
+
+  _securityChange: function _securityChange(aJson, aTab) {
+    // Don't need to do anything if the data we use to update the UI hasn't changed
+    if (aTab.state == aJson.state && !aTab.hostChanged)
+      return;
+
+    aTab.hostChanged = false;
+    aTab.state = aJson.state;
+
+    if (aTab == Browser.selectedTab) {
+      IdentityUI.checkIdentity();
+    }
+  },
+
+  _locationChange: function _locationChange(aJson, aTab) {
+    let spec = aJson.location;
+    let location = spec.split("#")[0]; // Ignore fragment identifier changes.
+
+    if (aTab == Browser.selectedTab)
+      BrowserUI.updateURI();
+
+    let locationHasChanged = (location != aTab.browser.lastLocation);
+    if (locationHasChanged) {
+      Browser.getNotificationBox(aTab.browser).removeTransientNotifications();
+      aTab.resetZoomLevel();
+      aTab.hostChanged = true;
+      aTab.browser.lastLocation = location;
+      aTab.browser.userTypedValue = "";
+      aTab.browser.appIcon = { href: null, size:-1 };
+
+#ifdef MOZ_CRASHREPORTER
+      if (CrashReporter.enabled)
+        CrashReporter.annotateCrashReport("URL", spec);
+#endif
+      this._waitForLoad(aTab);
+    }
+
+    let event = document.createEvent("UIEvents");
+    event.initUIEvent("URLChanged", true, false, window, locationHasChanged);
+    aTab.browser.dispatchEvent(event);
+  },
+
+  _waitForLoad: function _waitForLoad(aTab) {
+    let browser = aTab.browser;
+
+    aTab._firstPaint = false;
+
+    browser.messageManager.addMessageListener("Browser:FirstPaint", function firstPaintListener(aMessage) {
+      browser.messageManager.removeMessageListener(aMessage.name, arguments.callee);
+      aTab._firstPaint = true;
+      aTab.scrolledAreaChanged(true);
+      aTab.updateThumbnailSource();
+    });
+  },
+
+  _networkStart: function _networkStart(aJson, aTab) {
+    aTab.startLoading();
+
+    if (aTab == Browser.selectedTab) {
+      BrowserUI.update(TOOLBARSTATE_LOADING);
+
+      // We should at least show something in the URLBar until
+      // the load has progressed further along
+      if (aTab.browser.currentURI.spec == "about:blank")
+        BrowserUI.updateURI({ captionOnly: true });
+    }
+  },
+
+  _networkStop: function _networkStop(aJson, aTab) {
+    aTab.endLoading();
+
+    if (aTab == Browser.selectedTab) {
+      BrowserUI.update(TOOLBARSTATE_LOADED);
+    }
+  },
+
+  _windowStart: function _windowStart(aJson, aTab) {
+    this._progressStart(aJson, aTab);
+  },
+
+  _windowStop: function _windowStop(aJson, aTab) {
+    this._progressStop(aJson, aTab);
+  },
+
+  _progressStart: function _progressStart(aJson, aTab) {
+    // We will get multiple calls from _windowStart, so
+    // only process once.
+    if (this._progressActive)
+      return;
+
+    this._progressActive = true;
+
+    // 'Whoosh' in
+    this._progressCount = kProgressMarginStart;
+    Elements.progress.style.width = this._progressCount + "%"; 
+    Elements.progress.removeAttribute("fade");
+
+    // Create a pulse timer to keep things moving even if we don't
+    // collect any state changes.
+    setTimeout(function() {
+      WebProgress._progressStepTimer();
+    }, kHeartbeatDuration, this);
+  },
+
+  _stepProgressCount: function _stepProgressCount() {
+    // Step toward the end margin in smaller slices as we get closer
+    let left = kProgressMarginEnd - this._progressCount;
+    let step = left * .05;
+    this._progressCount += Math.ceil(step);
+
+    // Don't go past the 'whoosh out' margin.
+    if (this._progressCount > kProgressMarginEnd) {
+      this._progressCount = kProgressMarginEnd;
+    }
+  },
+
+  _progressStep: function _progressStep() {
+    if (!this._progressActive)
+      return;
+    this._stepProgressCount();
+    Elements.progress.style.width = this._progressCount + "%";
+  },
+
+  _progressStepTimer: function _progressStepTimer() {
+    if (!this._progressActive)
+      return;
+    this._progressStep();
+
+    setTimeout(function() {
+      WebProgress._progressStepTimer();
+    }, kHeartbeatDuration, this);
+  },
+
+  _progressStop: function _progressStop(aJson, aTab) {
+    this._progressActive = false;
+    // 'Whoosh out' and fade
+    Elements.progress.style.width = "100%"; 
+    Elements.progress.setAttribute("fade", true);
+  },
+
+  _progressTransEnd: function _progressTransEnd(data) {
+    if (!Elements.progress.hasAttribute("fade"))
+      return;
+    // Close out fade finished, reset
+    if (data.propertyName == "opacity") {
+      Elements.progress.style.width = "0px"; 
+    }
+  },
+};
+
+
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/appbar.js
@@ -0,0 +1,230 @@
+var Appbar = {
+  get appbar()        { return document.getElementById('appbar'); },
+  get consoleButton() { return document.getElementById('console-button'); },
+  get jsShellButton() { return document.getElementById('jsshell-button'); },
+  get zoomInButton()  { return document.getElementById('zoomin-button'); },
+  get zoomOutButton() { return document.getElementById('zoomout-button'); },
+  get starButton()    { return document.getElementById('star-button'); },
+  get pinButton()     { return document.getElementById('pin-button'); },
+  get moreButton()    { return document.getElementById('more-button'); },
+
+  // track selected/active richgrid/tilegroup - the context for contextual action buttons
+  activeTileset: null,
+
+  init: function Appbar_init() {
+    window.addEventListener('MozContextUIShow', this, false);
+    window.addEventListener('MozPrecisePointer', this, false);
+    window.addEventListener('MozImprecisePointer', this, false);
+
+    this._updateDebugButtons();
+    this._updateZoomButtons();
+
+    // tilegroup selection events for all modules get bubbled up
+    window.addEventListener("selectionchange", this, false);
+  },
+
+  handleEvent: function Appbar_handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case 'MozContextUIShow':
+        this._updatePinButton();
+        this._updateStarButton();
+        break;
+      case 'MozPrecisePointer':
+      case 'MozImprecisePointer':
+        this._updateZoomButtons();
+        break;
+      case "selectionchange":
+        let nodeName = aEvent.target.nodeName;
+        if ('richgrid' === nodeName) {
+          this._onTileSelectionChanged(aEvent);
+        }
+        break;
+    }
+  },
+
+  onDownloadButton: function() {
+    PanelUI.show("downloads-container");
+    ContextUI.dismiss();
+  },
+
+  onZoomOutButton: function() {
+    Browser.zoom(1);
+  },
+
+  onZoomInButton: function() {
+    Browser.zoom(-1);
+  },
+
+  onPinButton: function() {
+    if (this.pinButton.checked) {
+      Browser.pinSite();
+    } else {
+      Browser.unpinSite();
+    }
+  },
+
+  onStarButton: function(aValue) {
+    if (aValue === undefined) {
+      aValue = this.starButton.checked;
+    }
+
+    if (aValue) {
+      Browser.starSite(function () {
+        Appbar._updateStarButton();
+      });
+    } else {
+      Browser.unstarSite(function () {
+        Appbar._updateStarButton();
+      });
+    }
+  },
+
+  onMoreButton: function(aEvent) {
+      var typesArray = ["find-in-page"];
+      try {
+        // If we have a valid http or https URI then show the view on desktop
+        // menu item.
+        var uri = Services.io.newURI(Browser.selectedBrowser.currentURI.spec,
+                                     null, null);
+        if (uri.schemeIs('http') || uri.schemeIs('https')) {
+          typesArray.push("view-on-desktop");
+        }
+      } catch(ex) {
+      }
+
+      var x = this.moreButton.getBoundingClientRect().left;
+      var y = this.appbar.getBoundingClientRect().top;
+      ContextMenuUI.showContextMenu({
+        json: {
+          types: typesArray,
+          string: '',
+          xPos: x,
+          yPos: y,
+          forcePosition: true,
+          leftAligned: true,
+          bottomAligned: true
+      }
+
+      });
+  },
+
+  onViewOnDesktop: function() {
+    try {
+      // Make sure we have a valid URI so Windows doesn't prompt
+      // with an unrecognized command, select default program window
+      var uri = Services.io.newURI(Browser.selectedBrowser.currentURI.spec,
+                                   null, null);
+      if (uri.schemeIs('http') || uri.schemeIs('https')) {
+        MetroUtils.launchInDesktop(Browser.selectedBrowser.currentURI.spec, "");
+      }
+    } catch(ex) {
+    }
+  },
+
+  onConsoleButton: function() {
+    PanelUI.show("console-container");
+  },
+
+  onJSShellButton: function() {
+    // XXX for debugging, this only works when running on the desktop.
+    if (!MetroUtils.immersive)
+      window.openDialog("chrome://browser/content/shell.xul", "_blank",
+                        "all=no,scrollbars=yes,resizable=yes,dialog=no");
+  },
+
+  dispatchContextualAction: function(aActionName){
+    let activeTileset = this.activeTileset;
+    if (activeTileset) {
+      // fire event on the richgrid, others can listen
+      // but we keep coupling loose so grid doesn't need to know about appbar
+      let event = document.createEvent("Events");
+      event.action = aActionName;
+      event.initEvent("context-action", true, false);
+      activeTileset.dispatchEvent(event);
+
+      // done with this selection, explicitly clear it
+      activeTileset.clearSelection();
+    }
+    this.appbar.dismiss();
+  },
+
+  showContextualActions: function(aVerbs){
+    let doc = document;
+
+    // button element id to action verb lookup
+    let buttonsMap = new Map();
+    for (let verb of aVerbs) {
+      let id = verb + "-selected-button";
+      let buttonNode = doc.getElementById(id);
+      if (buttonNode) {
+        buttonsMap.set(id, verb);
+      } else {
+        Util.dumpLn("Appbar.showContextualActions: no button for " + verb);
+      }
+    }
+
+    // hide/show buttons as appropriate
+    let buttons = doc.querySelectorAll("#contextualactions-tray > toolbarbutton");
+
+    for (let btnNode of buttons) {
+      if (buttonsMap.has(btnNode.id)){
+        btnNode.hidden = false;
+      } else {
+        btnNode.hidden = true;
+      }
+    };
+
+    if (buttonsMap.size) {
+      // there are buttons to show
+      // TODO: show the contextual actions tray?
+    } else {
+      // 0 actions to show;
+      // TODO: hide the contextual actions tray entirely?
+    }
+  },
+
+  _onTileSelectionChanged: function _onTileSelectionChanged(aEvent){
+    let activeTileset = aEvent.target;
+
+    // deselect tiles in other tile groups
+    if (this.activeTileset && this.activeTileset !== activeTileset) {
+      this.activeTileset.clearSelection();
+    }
+    // keep track of which view is the target/scope for the contextual actions
+    this.activeTileset = activeTileset;
+
+    // ask the view for the list verbs/action-names it thinks are
+    // appropriate for the tiles selected
+    let contextActions = activeTileset.contextActions;
+    let verbs = [v for (v of contextActions)];
+
+    // could transition in old, new buttons?
+    this.showContextualActions(verbs);
+
+    if (verbs.length) {
+      this.appbar.show();
+    } else {
+      this.appbar.dismiss();
+    }
+  },
+
+  _updatePinButton: function() {
+    this.pinButton.checked = Browser.isSitePinned();
+  },
+
+  _updateStarButton: function() {
+    Browser.isSiteStarredAsync(function (isStarred) {
+      this.starButton.checked = isStarred;
+    }.bind(this));
+  },
+
+  _updateDebugButtons: function() {
+    this.consoleButton.disabled = !ConsolePanelView.enabled;
+    this.jsShellButton.disabled = MetroUtils.immersive;
+  },
+
+  _updateZoomButtons: function() {
+    let zoomDisabled = !InputSourceHelper.isPrecise;
+    this.zoomOutButton.disabled = this.zoomInButton.disabled = zoomDisabled;
+  }
+  };
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/appbar.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>  
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <binding id="appbarBinding">  
+    <content> 
+      <xul:toolbar anonid="toolbar"><children/></xul:toolbar>
+    </content>
+
+    <implementation implements="nsIDOMEventListener">
+      <constructor>
+        <![CDATA[
+          window.addEventListener('MozContextUIShow', this);
+          window.addEventListener('MozContextUIDismiss', this);
+          window.addEventListener('MozAppbarDismiss', this);
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+          window.removeEventListener('MozContextUIShow', this);
+          window.removeEventListener('MozContextUIDismiss', this);
+          window.removeEventListener('MozAppbarDismiss', this);
+        ]]>
+      </destructor>
+
+      <field name="sticky">false</field>
+      <field name="_toolbar" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "toolbar");</field>
+
+      <property name="isShowing" readonly="true">
+        <getter>
+          <![CDATA[
+            return this.getAttribute("visible") == "true";
+          ]]>
+        </getter>
+      </property>
+      
+      <method name="dismiss">
+        <body>
+          <![CDATA[
+            this.removeAttribute("visible");
+          ]]>
+        </body>
+      </method>
+
+      <method name="show">
+        <body>
+          <![CDATA[
+            this.setAttribute("visible", "true");
+          ]]>
+        </body>
+      </method>
+
+      <method name="toggle">
+        <body>
+          <![CDATA[
+            if (this.getAttribute("visible") === "true") {
+              this.dismiss();
+            } else {
+              this.show();
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            switch (aEvent.type) {
+              case 'MozContextUIShow':
+                this.show();
+                break;
+              case 'MozAppbarDismiss':
+              case 'MozContextUIDismiss':
+                this.dismiss();
+                break;
+            }
+          ]]>
+        </body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <!-- Work around for bug 835175 -->
+      <handler event="click">false;</handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/arrowbox.xml
@@ -0,0 +1,274 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="arrowbox" extends="xul:box">
+    <content orient="vertical">
+      <xul:box anonid="container" class="panel-arrowcontainer" flex="1">
+        <xul:box anonid="arrowbox" class="panel-arrowbox" dir="ltr">
+          <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="type"/>
+        </xul:box>
+        <xul:scrollbox anonid="arrowcontent" class="panel-arrowcontent" flex="1">
+          <xul:box class="panel-inner-arrowcontent" xbl:inherits="align,dir,orient,pack,flex">
+            <children/>
+          </xul:box>
+        </xul:scrollbox>
+      </xul:box>
+    </content>
+    <implementation implements="nsIDOMEventListener">
+      <constructor>
+        <![CDATA[
+          window.addEventListener("resize", this._eventHandler, false);
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+          window.removeEventListener("resize", this._eventHandler, false);
+        ]]>
+      </destructor>
+
+      <field name="anonScrollBox" readonly="true"><![CDATA[
+        // Expose the anyonymous scrollbox so ScrollUtils.getScrollboxFromElement can find it.
+        document.getAnonymousElementByAttribute(this, "anonid", "arrowcontent");
+      ]]></field>
+
+      <property name="offset" onget="return parseInt(this.getAttribute('offset')) || 0;"
+                              onset="this.setAttribute('offset', val); return val;"/>
+
+      <method name="_updateArrow">
+         <parameter name="popupRect"/>
+         <parameter name="targetRect"/>
+         <parameter name="horizPos"/>
+         <parameter name="vertPos"/>
+         <body>
+            <![CDATA[
+              let arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
+              if (!popupRect || !targetRect) {
+                arrow.hidden = true;
+                return;
+              }
+
+              let container = document.getAnonymousElementByAttribute(this, "anonid", "container");
+              let content = document.getAnonymousElementByAttribute(this, "anonid", "arrowcontent");
+              let arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
+
+              // If the content of the arrowbox if taller than the available
+              // screen space, force a maximum height
+              this.style.minHeight = "";
+              content.style.overflow = "visible";
+              const kBottomMargin = 64;
+              let contentRect = content.firstChild.getBoundingClientRect();
+              if ((contentRect.height + contentRect.top + kBottomMargin) > window.innerHeight) {
+                content.style.overflow = "hidden";
+                this.style.minHeight = (window.innerHeight - parseInt(this.top) - kBottomMargin) + "px";
+              }
+
+              let HALF_ARROW_WIDTH = 16;
+
+              let anchorClass = "";
+              let hideArrow = false;
+              if (horizPos == 0) {
+                container.orient = "vertical";
+                arrowbox.orient = "";
+                if (vertPos == 0) {
+                  hideArrow = true;
+                } else {
+                  let anchorPosX = 0.5;
+                  // check for hasAttribute because, in some cases, anchorNode is actually a rect
+                  if (this.anchorNode && this.anchorNode.hasAttribute && this.anchorNode.hasAttribute("anchorPosX"))
+                     anchorPosX = parseFloat(this.anchorNode.getAttribute("anchorPosX")) || 0.5;
+                  arrowbox.style.marginLeft = ((targetRect.left - popupRect.left) + (targetRect.width * anchorPosX) - HALF_ARROW_WIDTH) + "px";
+                  if (vertPos == 1) {
+                    container.dir = "normal"; 
+                    anchorClass = "top";
+                  } else if (vertPos == -1) {
+                    container.dir = "reverse"; 
+                    anchorClass = "bottom";
+                  }
+                }
+              } else if (vertPos == 0) {
+                container.orient = "";
+                arrowbox.orient = "vertical";
+                  let anchorPosY = 0.5;
+                  // check for hasAttribute because, in some cases, anchorNode is actually a rect
+                  if (this.anchorNode && this.anchorNode.hasAttribute && this.anchorNode.hasAttribute("anchorPosY"))
+                     anchorPosY = parseFloat(this.anchorNode.getAttribute("anchorPosY")) || 0.5;
+                arrowbox.style.marginTop = ((targetRect.top - popupRect.top) + (targetRect.height * anchorPosY) - HALF_ARROW_WIDTH) + "px";
+                if (horizPos == 1) {
+                  container.dir = "ltr"; 
+                  anchorClass = "left";
+                } else if (horizPos == -1) {
+                  container.dir = "rtl";
+                  anchorClass = "right";
+                }
+              } else {
+                hideArrow = true;
+              }
+              arrow.hidden = hideArrow;
+              arrow.setAttribute("side", anchorClass);
+            ]]>
+         </body>
+      </method>
+      <field name="anchorNode">null</field>
+      <method name="anchorTo">
+         <parameter name="aAnchorNode"/>
+         <parameter name="aPosition"/>
+         <body>
+            <![CDATA[
+              if (!aAnchorNode) {
+                this._updateArrow(null, null, 0, 0);
+                return;
+              }
+
+              this.anchorNode = aAnchorNode;
+              this.position = aPosition;
+
+              let anchorRect = aAnchorNode.getBoundingClientRect();
+              let popupRect = new Rect(0,0,0,0);
+              for (let i = 0; i < this.childNodes.length; i++) {
+                popupRect.expandToContain(Rect.fromRect(this.childNodes[i].getBoundingClientRect()));
+              }
+              let offset = this.offset;
+              let horizPos = 0;
+              let vertPos = 0;
+
+              if (aPosition) {
+                let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+                let isRtl = chromeReg.isLocaleRTL("global");
+                let left = 0;
+                let top = 0;
+
+                switch (aPosition) {
+                  case "before_start":
+                    left = isRtl ? anchorRect.right - popupRect.width : anchorRect.left;
+                    top = anchorRect.top + offset - popupRect.height;
+                    vertPos = -1;
+                    break;
+                  case "before_end":
+                    left = isRtl ? anchorRect.left : anchorRect.right - popupRect.width;
+                    top = anchorRect.top + offset - popupRect.height;
+                    vertPos = -1;
+                    break;
+                  case "after_start":
+                    left = isRtl ? anchorRect.right - popupRect.width : anchorRect.left;
+                    top = anchorRect.bottom - offset;
+                    vertPos = 1;
+                    break;
+                  case "after_end":
+                    left = isRtl ? anchorRect.left : anchorRect.right - popupRect.width;
+                    top = anchorRect.bottom - offset;
+                    vertPos = 1;
+                    break;
+                  case "start_before":
+                    left = isRtl ? anchorRect.right : anchorRect.left - popupRect.width - offset;
+                    top = anchorRect.top;
+                    horizPos = -1;
+                    break;
+                  case "start_after":
+                    left = isRtl ? anchorRect.right : anchorRect.left - popupRect.width - offset;
+                    top = anchorRect.bottom - popupRect.height;
+                    horizPos = -1;
+                    break;
+                  case "end_before":
+                    left = isRtl ? anchorRect.left - popupRect.width - offset : anchorRect.right;
+                    top = anchorRect.top;
+                    horizPos = 1;
+                    break;
+                  case "end_after":
+                    left = isRtl ? anchorRect.left - popupRect.width - offset : anchorRect.right;
+                    top = anchorRect.bottom - popupRect.height;
+                    horizPos = 1;
+                    break;
+                  case "overlap":
+                    left = isRtl ? anchorRect.right - popupRect.width + offset : anchorRect.left + offset ;
+                    top = anchorRect.top + offset ;
+                    break;
+                }
+                if (top == 0) top = 1;
+                if (left == 0) left = 1;
+
+                if (left + popupRect.width > window.innerWidth)
+                  left = window.innerWidth - popupRect.width;
+                else if (left < 0)
+                  left = 1;
+
+                popupRect.left = left;
+                this.setAttribute("left", left);
+                popupRect.top = top;
+                this.setAttribute("top", top);
+              } else {
+                horizPos = (Math.round(popupRect.right) <= Math.round(anchorRect.left + offset)) ? -1 :
+                               (Math.round(popupRect.left) >= Math.round(anchorRect.right - offset)) ? 1 : 0;
+                vertPos = (Math.round(popupRect.bottom) <= Math.round(anchorRect.top + offset)) ? -1 :
+                              (Math.round(popupRect.top) >= Math.round(anchorRect.bottom - offset)) ? 1 : 0;
+              }
+              this._updateArrow(popupRect, anchorRect, horizPos, vertPos);
+            ]]>
+         </body>
+      </method>
+
+      <method name="pointLeftAt">
+         <parameter name="targetNode"/>
+         <body>
+            <![CDATA[
+              if (!targetNode) {
+                this._updateArrow(null, null, 0, 0);
+                return;
+              }
+
+              let popupRect = this.getBoundingClientRect();
+              let targetRect = targetNode.getBoundingClientRect();
+              this._updateArrow(popupRect, targetRect, 1, 0);
+            ]]>
+         </body>
+      </method>
+
+      <method name="pointRightAt">
+         <parameter name="targetNode"/>
+         <body>
+            <![CDATA[
+              if (!targetNode) {
+                this._updateArrow(null, null, 0, 0);
+                return;
+              }
+
+              let popupRect = this.getBoundingClientRect();
+              let targetRect = targetNode.getBoundingClientRect();
+
+              this._updateArrow(popupRect, targetRect, -1, 0);
+            ]]>
+         </body>
+      </method>
+
+      <field name="_eventHandler"><![CDATA[
+        ({
+          self: this,
+          handleEvent: function handleEvent(aEvent) {
+            // We need to reset the margins because the previous values could
+            // cause the arrowbox to size incorrectly.
+            let self = this.self;
+            switch (aEvent.type) {
+              case "resize":
+                // Do nothing if there's no anchorNode
+                if (!self.anchorNode)
+                  break;
+                let arrowbox = document.getAnonymousElementByAttribute(self, "anonid", "arrowbox");
+                arrowbox.style.marginLeft = "0px";
+                arrowbox.style.marginTop = "0px";
+                self.anchorTo(self.anchorNode, self.position);
+                break;
+            }
+          }
+        })
+      ]]></field>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/autocomplete.xml
@@ -0,0 +1,445 @@
+<?xml version="1.0" encoding="Windows-1252" ?>
+<!-- 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/. -->
+
+
+<!DOCTYPE bindings [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="autocomplete" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+    <implementation>
+      <constructor>
+          <![CDATA[
+            this.minResultsForPopup = 0;
+            this.popup._input = this;
+          ]]>
+      </constructor>
+      <method name="openPopup">
+        <body>
+          <![CDATA[
+            this.popup.openAutocompletePopup(this, null);
+          ]]>
+        </body>
+      </method>
+
+      <method name="closePopup">
+        <body>
+          <![CDATA[
+            this.popup.closePopup(this, null);
+          ]]>
+        </body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="dblclick" phase="capturing">
+        <![CDATA[
+          let selectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
+          if (selectAll)
+            this.select();
+        ]]>
+      </handler>
+
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, true);
+        ]]>
+      </handler>
+      
+      <handler event="keypress" phase="capturing" keycode="VK_RETURN">
+        <![CDATA[
+          event.preventDefault();
+          event.stopPropagation();
+
+          this.popup.handleCompletion();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="autocomplete-popup">
+    <content orient="horizontal">
+      <xul:vbox id="results-vbox" class="meta-section viewable-height">
+        <xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
+        <richgrid id="results-richgrid" anonid="results" seltype="single" flex="1"/>
+      </xul:vbox>
+
+      <xul:vbox id="searches-vbox" class="meta-section viewable-height">
+        <xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
+        <richgrid id="searches-richgrid" anonid="searches" seltype="single" flex="1"/>
+      </xul:vbox>
+    </content>
+
+    <implementation implements="nsIAutoCompletePopup, nsIObserver">
+      <constructor>
+        <![CDATA[
+          Services.obs.addObserver(this, "browser-search-engine-modified", false);
+          this.updateSearchEngines();
+          this._results.controller = this;
+          this._searches.controller = this;
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+          Services.obs.removeObserver(this, "browser-search-engine-modified");
+        ]]>
+      </destructor>
+
+      <method name="handleItemClick">
+        <parameter name="aItem"/>
+        <body>
+          <![CDATA[
+            if (aItem.control == this._searches) {
+              let engineName = aItem.getAttribute("value");
+              BrowserUI.doOpenSearch(engineName);
+            } else {
+              let url = aItem.getAttribute("value");
+              Browser.loadURI(url);
+            }
+          ]]>
+        </body>
+      </method>
+
+    <!-- nsIAutocompleteInput -->
+
+      <field name="_input">null</field>
+      <field name="_popupOpen">false</field>
+
+      <property name="overrideValue" readonly="true" onget="return null;"/>
+
+      <property name="selectedItem">
+        <getter>
+          <![CDATA[
+            return this._isGridBound(this._results) ? this._results.selectedItem : null;
+          ]]>
+        </getter>
+        <setter>
+          <![CDATA[
+            return this._isGridBound(this._results) ? this._results.selectedItem : null;
+          ]]>
+        </setter>
+      </property>
+      <property name="selectedIndex">
+        <getter>
+          <![CDATA[
+            return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
+          ]]>
+        </getter>
+        <setter>
+          <![CDATA[
+            return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
+          ]]>
+        </setter>
+      </property>
+
+      <property name="input" readonly="true" onget="return this._input;"/>
+      <property name="popupOpen" readonly="true" onget="return this._popupOpen;"/>
+      <property name="_matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
+
+      <method name="openAutocompletePopup">
+        <parameter name="aInput"/>
+        <parameter name="aElement"/>
+        <body>
+          <![CDATA[
+            if (this._popupOpen)
+              return;
+
+            ContextUI.dismissAppbar();
+
+            this._input = aInput;
+            this._popupOpen = true;
+            this._grid = this._results;
+
+            this.clearSelection();
+            this.invalidate();
+            this._fire("autocompletestart");
+          ]]>
+        </body>
+      </method>
+
+      <method name="gridBoundCallback">
+        <body>
+          <![CDATA[
+            this.updateResults();
+          ]]>
+        </body>
+      </method>
+
+      <method name="closePopup">
+        <body>
+          <![CDATA[
+            if (!this._popupOpen)
+              return;
+
+            this.input.controller.stopSearch();
+            this._popupOpen = false;
+            this._fire("autocompleteend");
+          ]]>
+        </body>
+      </method>
+
+      <method name="invalidate">
+        <body>
+          <![CDATA[
+            if (!this._popupOpen)
+              return;
+
+            this.updateResults();
+            this.updateSearchEngineSubtitles();
+          ]]>
+        </body>
+      </method>
+
+      <method name="selectBy">
+        <parameter name="aReverse"/>
+        <parameter name="aPage"/>
+        <body>
+          <![CDATA[
+            let handleOnSelect = this._handleOnSelect;
+            this._handleOnSelect = false;
+
+            // TODO <jwilde>: Pressing page up/down should jump between rows,
+            //                not just items in the grid
+
+            // Move between grids if we're at the edge of one
+            if ((this._grid.isSelectionAtEnd && !aReverse) ||
+                (this._grid.isSelectionAtStart && aReverse)) {
+              let index = aReverse ? this._otherGrid.itemCount - 1 : 0;
+              this._otherGrid.selectedIndex = index;
+            } else {
+              this._grid.offsetSelection(aReverse ? -1 : 1);
+            }
+
+            this._handleOnSelect = handleOnSelect;
+          ]]>
+        </body>
+      </method>
+
+    <!-- nsIObserver -->
+
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+          <![CDATA[
+            if (aTopic != "browser-search-engine-modified")
+              return;
+
+            switch (aData) {
+              case "engine-added":
+              case "engine-removed":
+              case "engine-changed":
+                this.updateSearchEngines();
+                break;
+              case "engine-current":
+                // Not relevant
+                break;
+            }
+          ]]>
+        </body>
+      </method>
+
+    <!-- Interface for updating various components of the popup. -->
+
+      <method name="updateResults">
+        <body>
+          <![CDATA[
+            if (!this._isGridBound(this._results))
+              return;
+            if (!this.input)
+              return;
+
+            let controller = this.input.controller;
+            let lastMatch = this._matchCount - 1;
+            let iterCount = Math.max(this._results.itemCount, this._matchCount);
+
+            // Swap out existing items for new search hit results
+            for (let i = 0; i < iterCount; i++) {
+              if (i > lastMatch) {
+                let lastItem = this._results.itemCount - 1;
+                this._results.removeItemAt(lastItem);
+                continue;
+              }
+
+              let value = controller.getValueAt(i);
+              let label = controller.getCommentAt(i) || value;
+              let iconURI = controller.getImageAt(i);
+
+              let item = this._results.getItemAtIndex(i);
+              if (item == null) {
+                item = this._results.appendItem(label, value);
+                item.setAttribute("autocomplete", "true");
+              } else {
+                item.setAttribute("label", label);
+                item.setAttribute("value", value);
+              }
+
+              item.setAttribute("iconURI", iconURI);
+            }
+
+            this._results.arrangeItems();
+          ]]>
+        </body>
+      </method>
+
+      <method name="updateSearchEngines">
+        <body><![CDATA[
+          Services.search.init(this._onSearchServiceInit.bind(this));
+        ]]></body>
+      </method>
+
+      <method name="_onSearchServiceInit">
+        <body>
+          <![CDATA[
+            if (!this._isGridBound(this._searches))
+              return;
+
+            this._engines = Services.search.getVisibleEngines();
+
+            while (this._searches.itemCount > 0)
+              this._searches.removeItemAt(0);
+
+            this._engines.forEach(function (anEngine) {
+              let item = this._searches.appendItem(anEngine.name, anEngine.name);
+              item.setAttribute("autocomplete", "true");
+              item.setAttribute("search", "true");
+
+              let iconURI = anEngine.iconURI ? anEngine.iconURI.spec : "";
+              item.setAttribute("iconURI", iconURI);
+            }.bind(this));
+
+            this._searches.arrangeItems();
+          ]]>
+        </body>
+      </method>
+
+      <method name="updateSearchEngineSubtitles">
+        <body>
+          <![CDATA[
+            if (!this._isGridBound(this._searches))
+              return;
+
+            let searchString = this.input.controller.searchString;
+            let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1);
+
+            for (let i = 0, len = this._searches.itemCount; i < len; i++) {
+              let item = this._searches.getItemAtIndex(i);
+              item.setAttribute("label", label);
+            }
+
+            this._searches.arrangeItems();
+          ]]>
+        </body>
+      </method>
+
+    <!-- Interface for handling actions across grids -->
+
+      <method name="handleCompletion">
+        <body>
+          <![CDATA[
+            if (this._grid == this._results) {
+              this.input.controller.handleEnter(false);
+              return;
+            }
+
+            if (this._grid == this._searches) {
+              let engine = this._engines[this._grid.selectedIndex];
+              BrowserUI.doOpenSearch(engine.name);
+              this.closePopup();
+              return;
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="clearSelection">
+        <body>
+          <![CDATA[
+            if (this._isGridBound(this._results))
+              this._results.clearSelection();
+
+            if (this._isGridBound(this._searches))
+              this._searches.clearSelection();
+          ]]>
+        </body>
+      </method>
+
+    <!-- Helpers -->
+
+      <field name="_engines">[]</field>
+      <field name="_handleOnSelect">true</field>
+      <field name="_grid">null</field>
+
+      <property name="_results" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'results');"/>
+      <property name="_searches" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'searches');"/>
+
+      <property name="_otherGrid" readonly="true">
+        <getter>
+          <![CDATA[
+            return (this._grid == this._results) ? this._searches : this._results;
+          ]]>
+        </getter>
+      </property>
+
+      <method name="_isGridBound">
+        <parameter name="aGrid"/>
+        <body>
+          <![CDATA[
+            return aGrid.itemCount != undefined;
+          ]]>
+        </body>
+      </method>
+
+      <method name="_fire">
+        <parameter name="aName"/>
+        <body>
+          <![CDATA[
+            let event = document.createEvent("Events");
+            event.initEvent(aName, true, false);
+            this.dispatchEvent(event);
+          ]]>
+        </body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="select">
+        <![CDATA[
+          let grid = event.originalTarget;
+          if (grid != this._grid) {
+            if (this._grid.clearSelection)
+              this._grid.clearSelection();
+            this._grid = grid;
+          }
+
+          if (this._handleOnSelect && this._results.selectedItem) {
+            BrowserUI.goToURI(
+              this._results.selectedItem.getAttribute("value"));
+            this.closePopup();
+          } else if (this._handleOnSelect) {
+            this.handleCompletion();
+          }
+        ]]>
+      </handler>
+
+      <handler event="contentgenerated">
+        <![CDATA[
+          let grid = event.originalTarget;
+          if (grid == this._searches)
+            this.updateSearchEngines();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/bindings.xml
@@ -0,0 +1,407 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<!-- ==================================================================
+     Events: When these elements are in chrome, they are not obscured
+     by the touch input overlay. When they are in content they may be
+     obscured depending on the type of input we receive.
+     chrome: regular mouse events, moz gesture events, tap events.
+     content: touch input - tap events and synthetic mouse events
+     generated by content code.  mouse input - regular mouse events,
+     moz gesture events, tap events.
+     ================================================================== -->
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:html="http://www.w3.org/1999/xhtml"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="richlistbox-batch" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
+    <handlers>
+      <handler event="scroll">
+        <![CDATA[
+          // if there no more items to insert, just return early
+          if (this._items.length == 0)
+            return;
+
+          if (this._contentScrollHeight == -1) {
+            let scrollheight = {};
+            this.scrollBoxObject.getScrolledSize({}, scrollheight);
+            this._contentScrollHeight = scrollheight.value;
+          }
+
+          let y = {};
+          this.scrollBoxObject.getPosition({}, y);
+          let scrollRatio = (y.value + this._childrenHeight) / this._contentScrollHeight;
+
+          // If we're scrolled 80% to the bottom of the list, append the next
+          // set of items
+          if (scrollRatio > 0.8)
+            this._insertItems();
+        ]]>
+      </handler>
+    </handlers>
+    <implementation>
+      <!-- Number of elements to add to the list initially. If there are more
+           than this many elements to display, only add them to the list once
+           the user has scrolled towards them. This is a performance
+           optimization to avoid locking up while attempting to append hundreds
+           of nodes to our richlistbox.
+      -->
+      <property name="batchSize" readonly="true" onget="return this.getAttribute('batch')"/>
+
+      <field name="_childrenHeight">this.scrollBoxObject.height;</field>
+      <field name="_items">[]</field>
+
+      <method name="setItems">
+        <parameter name="aItems"/>
+        <body><![CDATA[
+          this._items = aItems;
+          this._insertItems();
+        ]]></body>
+      </method>
+
+      <method name="_insertItems">
+        <body><![CDATA[
+          let items = this._items.splice(0, this.batchSize);
+          if (!items.length)
+            return; // no items to insert
+
+          let count = items.length;
+          for (let i = 0; i<count; i++)
+            this.appendChild(items[i]);
+
+
+          // make sure we recalculate the scrollHeight of the content
+          this._contentScrollHeight = -1;
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="richlistbox-contextmenu" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
+    <handlers>
+      <handler event="click" phase="bubble">
+        <![CDATA[
+          ContextMenuUI.hide();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <handlers>
+      <handler event="mousedown" phase="capturing">
+        <![CDATA[
+          // We'll get this if the user is interacting via the mouse
+          let domUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
+                                           getService(Ci.inIDOMUtils);
+          domUtils.setContentState(this, 0x00000001);
+        ]]>
+      </handler>
+      <handler event="click" phase="capturing">
+        <![CDATA[
+          // allow normal mouse event processing
+          if (!InputSourceHelper || InputSourceHelper.isPrecise)
+            return;
+          // trap this here, we'll rely on tap events
+          // event.stopPropagation();
+        ]]>
+      </handler>
+      <handler event="touchstart" phase="capturing">
+        <![CDATA[
+          // touch input event
+          let domUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
+                                           getService(Ci.inIDOMUtils);
+          domUtils.setContentState(this, 0x00000001);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="content-navigator">
+    <content class="window-width content-navigator-box" orient="horizontal" pack="start">
+      <children includes="textbox"/>
+      <xul:button anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
+      <xul:button anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
+      <xul:spacer flex="1"/>
+      <xul:button anonid="close-button" class="content-navigator-item close-button" xbl:inherits="command=close"/>
+    </content>
+
+    <implementation>
+      <field name="_previousButton">
+        document.getAnonymousElementByAttribute(this, "anonid", "previous-button");
+      </field>
+
+      <field name="_nextButton">
+        document.getAnonymousElementByAttribute(this, "anonid", "next-button");
+      </field>
+
+      <field name="_closeButton">
+        document.getAnonymousElementByAttribute(this, "anonid", "close-button");
+      </field>
+
+      <method name="contentHasChanged">
+        <body><![CDATA[
+          if (!this.isActive)
+            return;
+
+          // There is a race condition with getBoundingClientRect and when the
+          // box is displayed, the padding is ignored in the size calculation.
+          // A nested timeouts below are used to workaround this problem.
+          this.getBoundingClientRect();
+
+          setTimeout(function(self) {
+            setTimeout(function(self) {
+              let height = Math.floor(self.getBoundingClientRect().height);
+              self.top = window.innerHeight - height;
+            }, 0, self);
+          }, 0, this);
+        ]]></body>
+      </method>
+
+      <property name="isActive" onget="return !!this.model;"/>
+
+      <field name="model">null</field>
+      <method name="show">
+        <parameter name="aModel" />
+        <body><![CDATA[
+          // call the hide callback of the current object if any
+          if (this.model && this.model.type != aModel.type)
+            this.model.hide();
+
+          this.setAttribute("type", aModel.type);
+          this.setAttribute("next", aModel.commands.next);
+          this.setAttribute("previous", aModel.commands.previous);
+          this.setAttribute("close", aModel.commands.close);
+
+          // buttons attributes sync with commands doesn not look updated when
+          // we dynamically switch the "command" attribute so we need to ensure
+          // the disabled state of the buttons is right when switching commands
+          this._previousButton.disabled = document.getElementById(aModel.commands.previous).getAttribute("disabled") == "true";
+          this._nextButton.disabled = document.getElementById(aModel.commands.next).getAttribute("disabled") == "true";
+
+          this.model = aModel;
+          this.contentHasChanged();
+        ]]></body>
+      </method>
+
+      <method name="hide">
+        <parameter name="aModel" />
+        <body><![CDATA[
+          this.removeAttribute("next");
+          this.removeAttribute("previous");
+          this.removeAttribute("close");
+          this.removeAttribute("type");
+
+          this.contentHasChanged();
+          this.model = null;
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="menulist" display="xul:box" extends="chrome://global/content/bindings/menulist.xml#menulist">
+    <handlers>
+      <handler event="mousedown" phase="capturing">
+        <![CDATA[
+          // Stop the normal menupopup from appearing
+          event.stopPropagation();
+        ]]>
+      </handler>
+
+      <handler event="click" button="0">
+        <![CDATA[
+          if (this.disabled || this.itemCount == 0)
+            return;
+
+          this.focus();
+          MenuControlUI.show(this);
+        ]]>
+      </handler>
+
+      <handler event="command" phase="capturing">
+        <![CDATA[
+          // The dropmark (button) fires a command event too. Don't forward that.
+          // Just forward the menuitem command events, which the toolkit version does.
+          if (event.target.parentNode.parentNode != this)
+            event.stopPropagation();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="chrome-select-option">
+    <content orient="horizontal" flex="1">
+      <xul:image class="chrome-select-option-image" anonid="check"/>
+      <xul:label anonid="label" xbl:inherits="value=label"/>
+    </content>
+
+    <implementation>
+      <property name="selected">
+        <getter>
+          <![CDATA[
+            return this.hasAttribute("selected");
+          ]]>
+        </getter>
+        <setter>
+          <![CDATA[
+            if (val)
+              this.setAttribute("selected", "true");
+            else
+              this.removeAttribute("selected");
+            return val;
+          ]]>
+        </setter>
+      </property>
+    </implementation>
+  </binding>
+
+  <binding id="select-button" extends="xul:box">
+    <content>
+      <svg:svg width="11px" version="1.1" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: -moz-calc(50% - 2px); left: 4px;">
+        <svg:polyline points="1 1 5 6 9 1" stroke="#414141" stroke-width="2" stroke-linecap="round" fill="transparent" stroke-linejoin="round"/>
+      </svg:svg>
+    </content>
+  </binding>
+
+  <binding id="textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
+    <handlers>
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, false);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="textarea" extends="chrome://global/content/bindings/textbox.xml#textarea">
+    <handlers>
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, false);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="timed-textbox" extends="chrome://global/content/bindings/textbox.xml#timed-textbox">
+    <handlers>
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, false);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#search-textbox">
+    <implementation>
+      <field name="_searchClear">
+        <![CDATA[
+          document.getAnonymousElementByAttribute(this, "class", "textbox-search-clear");
+        ]]>
+      </field>
+    </implementation>
+
+    <handlers>
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, false);
+        ]]>
+      </handler>
+
+      <handler event="text" phase="bubbling"><![CDATA[
+        // Listen for composition update, some VKB that does suggestions does not
+        // update directly the content of the field but in this case we want to
+        // search as soon as something is entered in the field
+        let evt = document.createEvent("Event");
+        evt.initEvent("input", true, false);
+        this.dispatchEvent(evt);
+      ]]></handler>
+
+      <handler event="click" phase="bubbling"><![CDATA[
+        // bug 629661. To reset the autosuggestions mechanism of Android, the
+        // textfield need to reset the IME state
+        if (event.originalTarget == this._searchClear) {
+          setTimeout(function(self) {
+            try {
+              let imeEditor = self.inputField.QueryInterface(Ci.nsIDOMNSEditableElement)
+                                             .editor
+                                             .QueryInterface(Ci.nsIEditorIMESupport);
+              if (imeEditor.composing)
+                imeEditor.forceCompositionEnd();
+            } catch(e) {}
+          }, 0, this);
+        }
+      ]]></handler>
+    </handlers>
+  </binding>
+
+  <binding id="numberbox" extends="chrome://global/content/bindings/numberbox.xml#numberbox">
+    <handlers>
+      <handler event="contextmenu" phase="capturing">
+        <![CDATA[
+          let box = this.inputField.parentNode;
+          box.showContextMenu(this, event, false);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="input-box" extends="xul:box">
+    <implementation>
+      <method name="showContextMenu">
+        <parameter name="aTextbox"/>
+        <parameter name="aEvent"/>
+        <parameter name="aIgnoreReadOnly"/>
+        <body><![CDATA[
+          let selectionStart = aTextbox.selectionStart;
+          let selectionEnd = aTextbox.selectionEnd;
+
+          let json = { types: ["input-text"], string: "" };
+          if (selectionStart != selectionEnd) {
+            json.types.push("copy");
+            json.string = aTextbox.value.slice(selectionStart, selectionEnd);
+          } else if (aTextbox.value) {
+            json.types.push("copy-all");
+            json.string = aTextbox.value;
+          }
+
+          if (selectionStart > 0 || selectionEnd < aTextbox.textLength)
+            json.types.push("select-all");
+
+          let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"].
+                                            getService(Ci.nsIClipboard);
+          let flavors = ["text/unicode"];
+          let hasData = clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard);
+
+          if (hasData && (!aTextbox.readOnly || aIgnoreReadOnly)) {
+            json.types.push("paste");
+            if (aTextbox.type == "url") {
+              json.types.push("paste-url");
+            }
+          }
+          json.xPos = aEvent.clientX;
+          json.yPos = aEvent.clientY;
+          json.source = aEvent.mozInputSource;
+          ContextMenuUI.showContextMenu({ target: aTextbox, json: json });
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/browser.js
@@ -0,0 +1,796 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let WebProgressListener = {
+  _lastLocation: null,
+  _firstPaint: false,
+
+  init: function() {
+    let flags = Ci.nsIWebProgress.NOTIFY_LOCATION |
+                Ci.nsIWebProgress.NOTIFY_SECURITY |
+                Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
+                Ci.nsIWebProgress.NOTIFY_STATE_NETWORK |
+                Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT;
+
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
+    webProgress.addProgressListener(this, flags);
+  },
+
+  onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+    if (content != aWebProgress.DOMWindow)
+      return;
+
+    sendAsyncMessage("Content:StateChange", {
+      contentWindowId: this.contentWindowId,
+      stateFlags: aStateFlags,
+      status: aStatus
+    });
+  },
+
+  onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
+    if (content != aWebProgress.DOMWindow)
+      return;
+
+    let spec = aLocationURI ? aLocationURI.spec : "";
+    let location = spec.split("#")[0];
+
+    let charset = content.document.characterSet;
+
+    sendAsyncMessage("Content:LocationChange", {
+      contentWindowId: this.contentWindowId,
+      documentURI:     aWebProgress.DOMWindow.document.documentURIObject.spec,
+      location:        spec,
+      canGoBack:       docShell.canGoBack,
+      canGoForward:    docShell.canGoForward,
+      charset:         charset.toString()
+    });
+
+    this._firstPaint = false;
+    let self = this;
+
+    // Keep track of hash changes
+    this.hashChanged = (location == this._lastLocation);
+    this._lastLocation = location;
+
+    // When a new page is loaded fire a message for the first paint
+    addEventListener("MozAfterPaint", function(aEvent) {
+      removeEventListener("MozAfterPaint", arguments.callee, true);
+
+      self._firstPaint = true;
+      let scrollOffset = ContentScroll.getScrollOffset(content);
+      sendAsyncMessage("Browser:FirstPaint", scrollOffset);
+    }, true);
+  },
+
+  onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+  },
+
+  onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
+    if (content != aWebProgress.DOMWindow)
+      return;
+
+    let serialization = SecurityUI.getSSLStatusAsString();
+
+    sendAsyncMessage("Content:SecurityChange", {
+      contentWindowId: this.contentWindowId,
+      SSLStatusAsString: serialization,
+      state: aState
+    });
+  },
+
+  get contentWindowId() {
+    return content.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIDOMWindowUtils)
+                  .currentInnerWindowID;
+  },
+
+  QueryInterface: function QueryInterface(aIID) {
+    if (aIID.equals(Ci.nsIWebProgressListener) ||
+        aIID.equals(Ci.nsISupportsWeakReference) ||
+        aIID.equals(Ci.nsISupports)) {
+        return this;
+    }
+
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+WebProgressListener.init();
+
+
+let SecurityUI = {
+  getSSLStatusAsString: function() {
+    let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+
+    if (status) {
+      let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+                      .getService(Ci.nsISerializationHelper);
+
+      status.QueryInterface(Ci.nsISerializable);
+      return serhelper.serializeToString(status);
+    }
+
+    return null;
+  }
+};
+
+let WebNavigation =  {
+  _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation),
+  _timer: null,
+
+  init: function() {
+    addMessageListener("WebNavigation:GoBack", this);
+    addMessageListener("WebNavigation:GoForward", this);
+    addMessageListener("WebNavigation:GotoIndex", this);
+    addMessageListener("WebNavigation:LoadURI", this);
+    addMessageListener("WebNavigation:Reload", this);
+    addMessageListener("WebNavigation:Stop", this);
+  },
+
+  receiveMessage: function(message) {
+    switch (message.name) {
+      case "WebNavigation:GoBack":
+        this.goBack();
+        break;
+      case "WebNavigation:GoForward":
+        this.goForward();
+        break;
+      case "WebNavigation:GotoIndex":
+        this.gotoIndex(message);
+        break;
+      case "WebNavigation:LoadURI":
+        this.loadURI(message);
+        break;
+      case "WebNavigation:Reload":
+        this.reload(message);
+        break;
+      case "WebNavigation:Stop":
+        this.stop(message);
+        break;
+    }
+  },
+
+  goBack: function() {
+    if (this._webNavigation.canGoBack)
+      this._webNavigation.goBack();
+  },
+
+  goForward: function() {
+    if (this._webNavigation.canGoForward)
+      this._webNavigation.goForward();
+  },
+
+  gotoIndex: function(message) {
+    this._webNavigation.gotoIndex(message.index);
+  },
+
+  loadURI: function(message) {
+    let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
+    this._webNavigation.loadURI(message.json.uri, flags, null, null, null);
+
+    let tabData = message.json;
+    if (tabData.entries) {
+      // We are going to load from history so kill the current load. We do not
+      // want the load added to the history anyway. We reload after resetting history
+      this._webNavigation.stop(this._webNavigation.STOP_ALL);
+      this._restoreHistory(tabData, 0);
+    }
+  },
+
+  reload: function(message) {
+    let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
+    this._webNavigation.reload(flags);
+  },
+
+  stop: function(message) {
+    let flags = message.json.flags || this._webNavigation.STOP_ALL;
+    this._webNavigation.stop(flags);
+  },
+
+  _restoreHistory: function _restoreHistory(aTabData, aCount) {
+    // We need to wait for the sessionHistory to be initialized and there
+    // is no good way to do this. We'll try a wait loop like desktop
+    try {
+      if (!this._webNavigation.sessionHistory)
+        throw new Error();
+    } catch (ex) {
+      if (aCount < 10) {
+        let self = this;
+        this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+        this._timer.initWithCallback(function(aTimer) {
+          self._timer = null;
+          self._restoreHistory(aTabData, aCount + 1);
+        }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+        return;
+      }
+    }
+
+    let history = this._webNavigation.sessionHistory;
+    if (history.count > 0)
+      history.PurgeHistory(history.count);
+    history.QueryInterface(Ci.nsISHistoryInternal);
+
+    // helper hashes for ensuring unique frame IDs and unique document
+    // identifiers.
+    let idMap = { used: {} };
+    let docIdentMap = {};
+
+    for (let i = 0; i < aTabData.entries.length; i++) {
+      if (!aTabData.entries[i].url)
+        continue;
+      history.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true);
+    }
+
+    // We need to force set the active history item and cause it to reload since
+    // we stop the load above
+    let activeIndex = (aTabData.index || aTabData.entries.length) - 1;
+    history.getEntryAtIndex(activeIndex, true);
+    history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
+  },
+
+  _deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
+    let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry);
+
+    shEntry.setURI(Services.io.newURI(aEntry.url, null, null));
+    shEntry.setTitle(aEntry.title || aEntry.url);
+    if (aEntry.subframe)
+      shEntry.setIsSubFrame(aEntry.subframe || false);
+    shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
+    if (aEntry.contentType)
+      shEntry.contentType = aEntry.contentType;
+    if (aEntry.referrer)
+      shEntry.referrerURI = Services.io.newURI(aEntry.referrer, null, null);
+
+    if (aEntry.cacheKey) {
+      let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
+      cacheKey.data = aEntry.cacheKey;
+      shEntry.cacheKey = cacheKey;
+    }
+
+    if (aEntry.ID) {
+      // get a new unique ID for this frame (since the one from the last
+      // start might already be in use)
+      let id = aIdMap[aEntry.ID] || 0;
+      if (!id) {
+        for (id = Date.now(); id in aIdMap.used; id++);
+        aIdMap[aEntry.ID] = id;
+        aIdMap.used[id] = true;
+      }
+      shEntry.ID = id;
+    }
+
+    if (aEntry.docshellID)
+      shEntry.docshellID = aEntry.docshellID;
+
+    if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
+      shEntry.stateData =
+        Cc["@mozilla.org/docshell/structured-clone-container;1"].
+        createInstance(Ci.nsIStructuredCloneContainer);
+
+      shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);
+    }
+
+    if (aEntry.scroll) {
+      let scrollPos = aEntry.scroll.split(",");
+      scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
+      shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
+    }
+
+    let childDocIdents = {};
+    if (aEntry.docIdentifier) {
+      // If we have a serialized document identifier, try to find an SHEntry
+      // which matches that doc identifier and adopt that SHEntry's
+      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
+      // for the document identifier.
+      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
+      if (!matchingEntry) {
+        matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
+        aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
+      } else {
+        shEntry.adoptBFCacheEntry(matchingEntry);
+        childDocIdents = matchingEntry.childDocIdents;
+      }
+    }
+
+    if (aEntry.owner_b64) {
+      let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+      let binaryData = atob(aEntry.owner_b64);
+      ownerInput.setData(binaryData, binaryData.length);
+      let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream);
+      binaryStream.setInputStream(ownerInput);
+      try { // Catch possible deserialization exceptions
+        shEntry.owner = binaryStream.readObject(true);
+      } catch (ex) { dump(ex); }
+    }
+
+    if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
+      for (let i = 0; i < aEntry.children.length; i++) {
+        if (!aEntry.children[i].url)
+          continue;
+
+        // We're getting sessionrestore.js files with a cycle in the
+        // doc-identifier graph, likely due to bug 698656.  (That is, we have
+        // an entry where doc identifier A is an ancestor of doc identifier B,
+        // and another entry where doc identifier B is an ancestor of A.)
+        //
+        // If we were to respect these doc identifiers, we'd create a cycle in
+        // the SHEntries themselves, which causes the docshell to loop forever
+        // when it looks for the root SHEntry.
+        //
+        // So as a hack to fix this, we restrict the scope of a doc identifier
+        // to be a node's siblings and cousins, and pass childDocIdents, not
+        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
+        // SHEntries with the same doc identifier have the same document iff
+        // they have the same parent or their parents have the same document.
+
+        shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i);
+      }
+    }
+    
+    return shEntry;
+  },
+
+  sendHistory: function sendHistory() {
+    // We need to package up the session history and send it to the sessionstore
+    let entries = [];
+    let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+    for (let i = 0; i < history.count; i++) {
+      let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false));
+      entries.push(entry);
+    }
+    let index = history.index + 1;
+    sendAsyncMessage("Content:SessionHistory", { entries: entries, index: index });
+  },
+
+  _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) {
+    let entry = { url: aEntry.URI.spec };
+
+    if (aEntry.title && aEntry.title != entry.url)
+      entry.title = aEntry.title;
+
+    if (!(aEntry instanceof Ci.nsISHEntry))
+      return entry;
+
+    let cacheKey = aEntry.cacheKey;
+    if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0)
+      entry.cacheKey = cacheKey.data;
+
+    entry.ID = aEntry.ID;
+    entry.docshellID = aEntry.docshellID;
+
+    if (aEntry.referrerURI)
+      entry.referrer = aEntry.referrerURI.spec;
+
+    if (aEntry.contentType)
+      entry.contentType = aEntry.contentType;
+
+    let x = {}, y = {};
+    aEntry.getScrollPosition(x, y);
+    if (x.value != 0 || y.value != 0)
+      entry.scroll = x.value + "," + y.value;
+
+    if (aEntry.owner) {
+      try {
+        let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIObjectOutputStream);
+        let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+        pipe.init(false, false, 0, 0xffffffff, null);
+        binaryStream.setOutputStream(pipe.outputStream);
+        binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
+        binaryStream.close();
+
+        // Now we want to read the data from the pipe's input end and encode it.
+        let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+        scriptableStream.setInputStream(pipe.inputStream);
+        let ownerBytes = scriptableStream.readByteArray(scriptableStream.available());
+        // We can stop doing base64 encoding once our serialization into JSON
+        // is guaranteed to handle all chars in strings, including embedded
+        // nulls.
+        entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
+      } catch (e) { dump(e); }
+    }
+
+    entry.docIdentifier = aEntry.BFCacheEntry.ID;
+
+    if (aEntry.stateData != null) {
+      entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
+      entry.structuredCloneVersion = aEntry.stateData.formatVersion;
+    }
+
+    if (!(aEntry instanceof Ci.nsISHContainer))
+      return entry;
+
+    if (aEntry.childCount > 0) {
+      entry.children = [];
+      for (let i = 0; i < aEntry.childCount; i++) {
+        let child = aEntry.GetChildAt(i);
+        if (child)
+          entry.children.push(this._serializeHistoryEntry(child));
+        else // to maintain the correct frame order, insert a dummy entry 
+          entry.children.push({ url: "about:blank" });
+
+        // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
+        if (/^wyciwyg:\/\//.test(entry.children[i].url)) {
+          delete entry.children;
+          break;
+        }
+      }
+    }
+
+    return entry;
+  }
+};
+
+WebNavigation.init();
+
+
+let DOMEvents =  {
+  init: function() {
+    addEventListener("DOMContentLoaded", this, false);
+    addEventListener("DOMTitleChanged", this, false);
+    addEventListener("DOMLinkAdded", this, false);
+    addEventListener("DOMWillOpenModalDialog", this, false);
+    addEventListener("DOMModalDialogClosed", this, true);
+    addEventListener("DOMWindowClose", this, false);
+    addEventListener("DOMPopupBlocked", this, false);
+    addEventListener("pageshow", this, false);
+    addEventListener("pagehide", this, false);
+  },
+
+  handleEvent: function(aEvent) {
+    let document = content.document;
+    switch (aEvent.type) {
+      case "DOMContentLoaded":
+        if (document.documentURIObject.spec == "about:blank")
+          return;
+
+        sendAsyncMessage("DOMContentLoaded", { });
+
+        // Send the session history now too
+        WebNavigation.sendHistory();
+        break;
+
+      case "pageshow":
+      case "pagehide": {
+        if (aEvent.target.defaultView != content)
+          break;
+
+        let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                                            .getInterface(Ci.nsIDOMWindowUtils);
+
+        let json = {
+          contentWindowWidth: content.innerWidth,
+          contentWindowHeight: content.innerHeight,
+          windowId: util.outerWindowID,
+          persisted: aEvent.persisted
+        };
+
+        // Clear onload focus to prevent the VKB to be shown unexpectingly
+        // but only if the location has really changed and not only the
+        // fragment identifier
+        let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+        if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) {
+          let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+          focusManager.clearFocus(content);
+        }
+
+        sendAsyncMessage(aEvent.type, json);
+        break;
+      }
+
+      case "DOMPopupBlocked": {
+        let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                          .getInterface(Ci.nsIDOMWindowUtils);
+        let json = {
+          windowId: util.outerWindowID,
+          popupWindowURI: {
+            spec: aEvent.popupWindowURI.spec,
+            charset: aEvent.popupWindowURI.originCharset
+          },
+          popupWindowFeatures: aEvent.popupWindowFeatures,
+          popupWindowName: aEvent.popupWindowName
+        };
+
+        sendAsyncMessage("DOMPopupBlocked", json);
+        break;
+      }
+
+      case "DOMTitleChanged":
+        sendAsyncMessage("DOMTitleChanged", { title: document.title });
+        break;
+
+      case "DOMLinkAdded":
+        let target = aEvent.originalTarget;
+        if (!target.href || target.disabled)
+          return;
+
+        let json = {
+          windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
+          href: target.href,
+          charset: document.characterSet,
+          title: target.title,
+          rel: target.rel,
+          type: target.type
+        };
+        
+        // rel=icon can also have a sizes attribute
+        if (target.hasAttribute("sizes"))
+          json.sizes = target.getAttribute("sizes");
+
+        sendAsyncMessage("DOMLinkAdded", json);
+        break;
+
+      case "DOMWillOpenModalDialog":
+      case "DOMModalDialogClosed":
+      case "DOMWindowClose":
+        let retvals = sendSyncMessage(aEvent.type, { });
+        for (let i in retvals) {
+          if (retvals[i].preventDefault) {
+            aEvent.preventDefault();
+            break;
+          }
+        }
+        break;
+    }
+  }
+};
+
+DOMEvents.init();
+
+let ContentScroll =  {
+  _scrollOffset: { x: 0, y: 0 },
+
+  init: function() {
+    addMessageListener("Content:SetCacheViewport", this);
+    addMessageListener("Content:SetWindowSize", this);
+
+    addEventListener("scroll", this, false);
+    addEventListener("pagehide", this, false);
+    addEventListener("MozScrolledAreaChanged", this, false);
+  },
+
+  getScrollOffset: function(aWindow) {
+    let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    let scrollX = {}, scrollY = {};
+    cwu.getScrollXY(false, scrollX, scrollY);
+    return { x: scrollX.value, y: scrollY.value };
+  },
+
+  getScrollOffsetForElement: function(aElement) {
+    if (aElement.parentNode == aElement.ownerDocument)
+      return this.getScrollOffset(aElement.ownerDocument.defaultView);
+    return { x: aElement.scrollLeft, y: aElement.scrollTop };
+  },
+
+  setScrollOffsetForElement: function(aElement, aLeft, aTop) {
+    if (aElement.parentNode == aElement.ownerDocument) {
+      aElement.ownerDocument.defaultView.scrollTo(aLeft, aTop);
+    } else {
+      aElement.scrollLeft = aLeft;
+      aElement.scrollTop = aTop;
+    }
+  },
+
+  receiveMessage: function(aMessage) {
+    let json = aMessage.json;
+    switch (aMessage.name) {
+      case "Content:SetCacheViewport": {
+        // Set resolution for root view
+        let rootCwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+        if (json.id == 1) {
+          rootCwu.setResolution(json.scale, json.scale);
+          if (!WebProgressListener._firstPaint)
+            break;
+        }
+
+        let displayport = new Rect(json.x, json.y, json.w, json.h);
+        if (displayport.isEmpty())
+          break;
+
+        // Map ID to element
+        let element = null;
+        try {
+          element = rootCwu.findElementWithViewId(json.id);
+        } catch(e) {
+          // This could give NS_ERROR_NOT_AVAILABLE. In that case, the
+          // presshell is not available because the page is reloading.
+        }
+
+        if (!element)
+          break;
+
+        let binding = element.ownerDocument.getBindingParent(element);
+        if (binding instanceof Ci.nsIDOMHTMLInputElement && binding.mozIsTextField(false))
+          break;
+
+        // Set the scroll offset for this element if specified
+        if (json.scrollX >= 0 && json.scrollY >= 0) {
+          this.setScrollOffsetForElement(element, json.scrollX, json.scrollY)
+          if (json.id == 1)
+            this._scrollOffset = this.getScrollOffset(content);
+        }
+
+        // Set displayport. We want to set this after setting the scroll offset, because
+        // it is calculated based on the scroll offset.
+        let scrollOffset = this.getScrollOffsetForElement(element);
+        let x = displayport.x - scrollOffset.x;
+        let y = displayport.y - scrollOffset.y;
+
+        if (json.id == 1) {
+          x = Math.round(x * json.scale) / json.scale;
+          y = Math.round(y * json.scale) / json.scale;
+        }
+
+        let win = element.ownerDocument.defaultView;
+        let winCwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+        winCwu.setDisplayPortForElement(x, y, displayport.width, displayport.height, element);
+        break;
+      }
+
+      case "Content:SetWindowSize": {
+        let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+        cwu.setCSSViewport(json.width, json.height);
+        break;
+      }
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "pagehide":
+        this._scrollOffset = { x: 0, y: 0 };
+        break;
+
+      case "scroll": {
+        let doc = aEvent.target;
+        if (doc != content.document)
+          break;
+
+        this.sendScroll();
+        break;
+      }
+
+      case "MozScrolledAreaChanged": {
+        let doc = aEvent.originalTarget;
+        if (content != doc.defaultView) // We are only interested in root scroll pane changes
+          return;
+
+        sendAsyncMessage("MozScrolledAreaChanged", {
+          width: aEvent.width,
+          height: aEvent.height,
+          left: aEvent.x + content.scrollX
+        });
+
+        // Send event only after painting to make sure content views in the parent process have
+        // been updated.
+        addEventListener("MozAfterPaint", function afterPaint() {
+          removeEventListener("MozAfterPaint", afterPaint, false);
+          sendAsyncMessage("Content:UpdateDisplayPort");
+        }, false);
+
+        break;
+      }
+    }
+  },
+
+  sendScroll: function sendScroll() {
+    let scrollOffset = this.getScrollOffset(content);
+    if (this._scrollOffset.x == scrollOffset.x && this._scrollOffset.y == scrollOffset.y)
+      return;
+
+    this._scrollOffset = scrollOffset;
+    sendAsyncMessage("scroll", scrollOffset);
+  }
+};
+
+ContentScroll.init();
+
+let ContentActive =  {
+  init: function() {
+    addMessageListener("Content:Activate", this);
+    addMessageListener("Content:Deactivate", this);
+  },
+
+  receiveMessage: function(aMessage) {
+    let json = aMessage.json;
+    switch (aMessage.name) {
+      case "Content:Deactivate":
+        docShell.isActive = false;
+        let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+        if (json.keepviewport)
+          break;
+        cwu.setDisplayPortForElement(0, 0, 0, 0, content.document.documentElement);
+        break;
+
+      case "Content:Activate":
+        docShell.isActive = true;
+        break;
+    }
+  }
+};
+
+ContentActive.init();
+
+/**
+ * Helper class for IndexedDB, child part. Listens using
+ * the observer service for events regarding IndexedDB
+ * prompts, and sends messages to the parent to actually
+ * show the prompts.
+ */
+let IndexedDB = {
+  _permissionsPrompt: "indexedDB-permissions-prompt",
+  _permissionsResponse: "indexedDB-permissions-response",
+
+  _quotaPrompt: "indexedDB-quota-prompt",
+  _quotaResponse: "indexedDB-quota-response",
+  _quotaCancel: "indexedDB-quota-cancel",
+
+  waitingObservers: [],
+
+  init: function IndexedDBPromptHelper_init() {
+    let os = Services.obs;
+    os.addObserver(this, this._permissionsPrompt, false);
+    os.addObserver(this, this._quotaPrompt, false);
+    os.addObserver(this, this._quotaCancel, false);
+    addMessageListener("IndexedDB:Response", this);
+  },
+
+  observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) {
+    if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) {
+      throw new Error("Unexpected topic!");
+    }
+
+    let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
+    let observer = requestor.getInterface(Ci.nsIObserver);
+
+    let contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+    let contentDocument = contentWindow.document;
+
+    if (aTopic == this._quotaCancel) {
+      observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION);
+      return;
+    }
+
+    // Remote to parent
+    sendAsyncMessage("IndexedDB:Prompt", {
+      topic: aTopic,
+      host: contentDocument.documentURIObject.asciiHost,
+      location: contentDocument.location.toString(),
+      data: aData,
+      observerId: this.addWaitingObserver(observer)
+    });
+  },
+
+  receiveMessage: function(aMessage) {
+    let payload = aMessage.json;
+    switch (aMessage.name) {
+      case "IndexedDB:Response":
+        let observer = this.getAndRemoveWaitingObserver(payload.observerId);
+        observer.observe(null, payload.responseTopic, payload.permission);
+    }
+  },
+
+  addWaitingObserver: function(aObserver) {
+    let observerId = 0;
+    while (observerId in this.waitingObservers)
+      observerId++;
+    this.waitingObservers[observerId] = aObserver;
+    return observerId;
+  },
+
+  getAndRemoveWaitingObserver: function(aObserverId) {
+    let observer = this.waitingObservers[aObserverId];
+    delete this.waitingObservers[aObserverId];
+    return observer;
+  }
+};
+
+IndexedDB.init();
+
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/browser.xml
@@ -0,0 +1,1089 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+  <!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
+  %findBarDTD;
+]>
+
+<bindings id="remoteBrowserBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="local-browser" extends="chrome://global/content/bindings/browser.xml#browser">
+    <implementation type="application/javascript" implements="nsIAccessibleProvider, nsIObserver, nsIDOMEventListener, nsIMessageListener">
+      <field name="_securityUI">null</field>
+      <property name="securityUI">
+        <getter><![CDATA[
+          return this._securityUI || {};
+        ]]></getter>
+        <setter><![CDATA[
+          this._securityUI = val;
+        ]]></setter>
+      </property>
+
+      <field name="_searchEngines">[]</field>
+      <property name="searchEngines"
+                onget="return this._searchEngines"
+                readonly="true"/>
+
+      <field name="_documentURI">null</field>
+      <property name="documentURI"
+                onget="return this._documentURI ? this._ios.newURI(this._documentURI, null, null) : null"
+                readonly="true"/>
+
+      <field name="contentWindowId">null</field>
+
+      <property name="messageManager"
+                onget="return this._frameLoader.messageManager;"
+                readonly="true"/>
+
+      <field name="_contentTitle">null</field>
+
+      <field name="_ios">
+         Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+      </field>
+
+      <field name="_messageListenerLocal"><![CDATA[
+        ({
+          self: this,
+          receiveMessage: function receiveMessage(aMessage) {
+            let self = this.self;
+            let json = aMessage.json;
+
+            switch (aMessage.name) {
+              case "DOMPopupBlocked":
+                self.onPopupBlocked(aMessage);
+                break;
+
+              case "pageshow":
+                self.onPageShow(aMessage);
+
+                if (!self.mIconURL && self._documentURI) {
+                  let iconURL = null;
+                  if (self.shouldLoadFavicon()) {
+                    // Use documentURI in the favicon construction so that we
+                    // do the right thing with about:-style error pages.  Bug 515188
+                    iconURL = self.documentURI.prePath + "/favicon.ico";
+                  }
+                  self.loadFavicon(iconURL, null);
+                }
+                break;
+
+              case "pagehide":
+                self.onPageHide(aMessage);
+                break;
+
+              case "DOMTitleChanged":
+                self._contentTitle = json.title;
+                break;
+
+              case "DOMLinkAdded":
+                // ignore results from subdocuments
+                if (json.windowId != self.contentWindowId)
+                  return;
+
+                let linkType = self._getLinkType(json);
+                switch(linkType) {
+                  case "icon":
+                    self.loadFavicon(json.href, json.charset);
+                    break;
+                  case "search":
+                    self._searchEngines.push({ title: json.title, href: json.href });
+                    break;
+                }
+                break;
+
+              case "MozScrolledAreaChanged": {
+                self._contentDocumentWidth = json.width;
+                self._contentDocumentHeight = json.height;
+                self._contentDocumentLeft = (json.left < 0) ? json.left : 0;
+
+                // Recalculate whether the visible area is actually in bounds
+                let view = self.getRootView();
+                view.scrollBy(0, 0);
+                break;
+              }
+
+              case "Content:UpdateDisplayPort": {
+                // Recalculate whether the visible area is actually in bounds
+                let view = self.getRootView();
+                view.scrollBy(0, 0);
+                view._updateCacheViewport();
+                break;
+              }
+            }
+          }
+        })
+      ]]></field>
+
+      <method name="loadFavicon">
+        <parameter name="aURL"/>
+        <parameter name="aCharset"/>
+        <body><![CDATA[
+            try { // newURI call is throwing for chrome URI
+              let iconURI = this._ios.newURI(aURL, aCharset, null);
+              if (gFaviconService.isFailedFavicon(iconURI))
+                return;
+
+              gFaviconService.setAndFetchFaviconForPage(this.currentURI, iconURI, true,
+                                                        gFaviconService.FAVICON_LOAD_NON_PRIVATE);
+              this.mIconURL = iconURI.spec;
+            } catch (e) {
+              this.mIconURL = null;
+            }
+        ]]></body>
+      </method>
+
+      <method name="shouldLoadFavicon">
+        <body><![CDATA[
+          let docURI = this.documentURI;
+          return (docURI && ("schemeIs" in docURI) &&
+                  (docURI.schemeIs("http") || docURI.schemeIs("https")));
+        ]]></body>
+      </method>
+
+      <method name="_getLinkType">
+        <parameter name="aLink" />
+        <body><![CDATA[
+          let type = "";
+          if (/\bicon\b/i.test(aLink.rel)) {
+            type = "icon";
+          }
+          else if (/\bsearch\b/i.test(aLink.rel) && aLink.type && aLink.title) {
+            let linkType = aLink.type.replace(/^\s+|\s*(?:;.*)?$/g, "").toLowerCase();
+            if (linkType == "application/opensearchdescription+xml" && /^(?:https?|ftp):/i.test(aLink.href)) {
+              type = "search";
+            }
+          }
+
+          return type;
+        ]]></body>
+      </method>
+
+      <field name="_webProgress"><![CDATA[
+        ({
+          _browser: this,
+
+          _init: function() {
+            this._browser.messageManager.addMessageListener("Content:StateChange", this);
+            this._browser.messageManager.addMessageListener("Content:LocationChange", this);
+            this._browser.messageManager.addMessageListener("Content:SecurityChange", this);
+          },
+
+          receiveMessage: function(aMessage) {
+            let json = aMessage.json;
+            switch (aMessage.name) {
+              case "Content:StateChange":
+                this._browser.updateWindowId(json.contentWindowId);
+                break;
+
+              case "Content:LocationChange":
+                try {
+                  let locationURI = this._browser._ios.newURI(json.location, null, null);
+                  this._browser.webNavigation._currentURI = locationURI;
+                  this._browser.webNavigation.canGoBack = json.canGoBack;
+                  this._browser.webNavigation.canGoForward = json.canGoForward;
+                  this._browser._charset = json.charset;
+                } catch(e) {}
+
+                if (this._browser.updateWindowId(json.contentWindowId)) {
+                  this._browser._documentURI = json.documentURI;
+                  this._browser._searchEngines = [];
+                }
+                break;
+
+              case "Content:SecurityChange":
+                let serhelper = Components.classes["@mozilla.org/network/serialization-helper;1"]
+                                .getService(Components.interfaces.nsISerializationHelper);
+                let SSLStatus = json.SSLStatusAsString ? serhelper.deserializeObject(json.SSLStatusAsString) : null;
+                if (SSLStatus) {
+                  SSLStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+                  // We must check the Extended Validation (EV) state here, on the chrome
+                  // process, because NSS is needed for that determination.
+                  if (SSLStatus && SSLStatus.isExtendedValidation) {
+                      json.state |= Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+                  }
+                }
+
+                let data = this._getIdentityData(SSLStatus);
+                this._browser._securityUI = {
+                  SSLStatus: SSLStatus ? {serverCert: data} : null,
+                  state: json.state
+                }
+                this._browser.updateWindowId(json.contentWindowId);
+                break;
+            }
+          },
+
+          /**
+           * Helper to parse out the important parts of the SSL cert for use in constructing
+           * identity UI strings
+           */
+          _getIdentityData: function(status) {
+            let result = {};
+
+            if (status) {
+              let cert = status.serverCert;
+
+              // Human readable name of Subject
+              result.subjectOrg = cert.organization;
+
+              // SubjectName fields, broken up for individual access
+              if (cert.subjectName) {
+                result.subjectNameFields = {};
+                cert.subjectName.split(",").forEach(function(v) {
+                  var field = v.split("=");
+                  if (field[1])
+                    this[field[0]] = field[1];
+                }, result.subjectNameFields);
+
+                // Call out city, state, and country specifically
+                result.city = result.subjectNameFields.L;
+                result.state = result.subjectNameFields.ST;
+                result.country = result.subjectNameFields.C;
+              }
+
+              // Human readable name of Certificate Authority
+              result.caOrg =  cert.issuerOrganization || cert.issuerCommonName;
+
+              if (!this._overrideService)
+                this._overrideService = Components.classes["@mozilla.org/security/certoverride;1"]
+                                        .getService(Components.interfaces.nsICertOverrideService);
+
+              // Check whether this site is a security exception.
+              let currentURI = this._browser.webNavigation._currentURI;
+              result.isException = !!this._overrideService.hasMatchingOverride(currentURI.asciiHost, currentURI.port, cert, {}, {});
+            }
+
+            return result;
+          }
+        })
+      ]]></field>
+
+      <property name="webProgress"
+                readonly="true"
+                onget="return null"/>
+
+      <method name="onPageShow">
+        <parameter name="aMessage"/>
+        <body>
+          <![CDATA[
+            this.attachFormFill();
+            if (this.pageReport) {
+              var json = aMessage.json;
+              var i = 0;
+              while (i < this.pageReport.length) {
+                // Filter out irrelevant reports.
+                if (this.pageReport[i].requestingWindowId == json.windowId)
+                  i++;
+                else
+                  this.pageReport.splice(i, 1);
+              }
+              if (this.pageReport.length == 0) {
+                this.pageReport = null;
+                this.updatePageReport();
+              }
+            }
+         ]]>
+        </body>
+      </method>
+
+      <method name="onPageHide">
+        <parameter name="aMessage"/>
+        <body>
+          <![CDATA[
+            if (this.pageReport) {
+              this.pageReport = null;
+              this.updatePageReport();
+            }
+            // Delete the feeds cache if we're hiding the topmost page
+            // (as opposed to one of its iframes).
+            if (this.feeds && aMessage.target == this)
+              this.feeds = null;
+
+            this._contentWindowWidth = aMessage.json.contentWindowWidth;
+            this._contentWindowHeight = aMessage.json.contentWindowHeight;
+         ]]>
+        </body>
+      </method>
+
+      <method name="onPopupBlocked">
+        <parameter name="aMessage"/>
+        <body>
+          <![CDATA[
+            if (!this.pageReport) {
+              this.pageReport = [];
+            }
+
+            let json = aMessage.json;
+            // XXX Replacing requestingWindow && requestingDocument affects
+            // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#500
+            var obj = {
+              requestingWindowId: json.windowId,
+              popupWindowURI: this._ios.newURI(json.popupWindowURI.spec, json.popupWindowURI.charset, null),
+              popupWindowFeatures: json.popupWindowFeatures,
+              popupWindowName: json.popupWindowName
+            };
+
+            this.pageReport.push(obj);
+            this.pageReport.reported = false;
+            this.updatePageReport();
+          ]]>
+        </body>
+      </method>
+
+      <field name="_frameLoader">null</field>
+      <field name="_contentViewManager">null</field>
+
+      <!-- Dimensions of content window -->
+      <field name="_contentWindowWidth">0</field>
+      <field name="_contentWindowHeight">0</field>
+      <property name="contentWindowWidth"
+                onget="return this._contentWindowWidth;"
+                readonly="true"/>
+      <property name="contentWindowHeight"
+                onget="return this._contentWindowHeight;"
+                readonly="true"/>
+
+      <!-- Dimensions of content document -->
+      <field name="_contentDocumentWidth">0</field>
+      <field name="_contentDocumentHeight">0</field>
+      <property name="contentDocumentWidth"
+                onget="return this._contentDocumentWidth;"
+                readonly="true"/>
+      <property name="contentDocumentHeight"
+                onget="return this._contentDocumentHeight;"
+                readonly="true"/>
+
+      <!-- If this attribute is negative this indicate the document is rtl and
+           some operations like panning or calculating the cache area should
+           take it into account. This is useless for non-remote browser -->
+      <field name="_contentDocumentLeft">0</field>
+
+      <!-- The ratio of device pixels to CSS pixels -->
+      <property name="scale"
+                onget="return 1;"
+                onset="return 1;"/>
+
+      <!-- These counters are used to update the cached viewport after they reach a certain
+           threshold when scrolling -->
+      <field name="_cacheRatioWidth">1</field>
+      <field name="_cacheRatioHeight">1</field>
+
+      <!-- Used in remote tabs only. -->
+      <method name="_updateCSSViewport">
+        <body/>
+      </method>
+
+      <!-- Sets size of CSS viewport, which affects how page is layout. -->
+      <method name="setWindowSize">
+        <parameter name="width"/>
+        <parameter name="height"/>
+        <body>
+          <![CDATA[
+            this._contentWindowWidth = width;
+            this._contentWindowHeight = height;
+            this.messageManager.sendAsyncMessage("Content:SetWindowSize", {
+              width: width,
+              height: height
+            });
+
+            // If the window size is changing, make sure the displayport is in sync
+            this.getRootView()._updateCacheViewport();
+          ]]>
+        </body>
+      </method>
+
+      <method name="getRootView">
+        <body>
+          <![CDATA[
+            return this._contentView;
+          ]]>
+        </body>
+      </method>
+
+      <field name="_contentViewPrototype"><![CDATA[
+        ({
+          _scrollbox: null,
+
+          init: function(aElement) {
+            this._scrollbox = aElement.scrollBoxObject;
+          },
+
+          isRoot: function() {
+            return false;
+          },
+
+          scrollBy: function(x, y) {
+            this._scrollbox.scrollBy(x,y);
+          },
+
+          scrollTo: function(x, y) {
+            this._scrollbox.scrollTo(x,y);
+          },
+
+          getPosition: function() {
+            let x = {}, y = {};
+            this._scrollbox.getPosition(x, y);
+            return { x: x.value, y: y.value };
+          }
+        })
+        ]]>
+      </field>
+
+      <method name="getViewAt">
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body>
+          <![CDATA[
+            let cwu = this.contentDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                                                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+            let elt = cwu.elementFromPoint(x, y, false, false);
+
+            while (elt && !elt.scrollBoxObject)
+              elt = elt.parentNode;
+ 
+            if (!elt)
+              return this._contentView;
+
+            let cv = Object.create(this._contentViewPrototype);
+            cv.init(elt);
+            return cv;
+          ]]>
+        </body>
+      </method>
+
+      <field name="_contentView"><![CDATA[
+        ({
+          self: this,
+
+          _updateCacheViewport: function() {
+          },
+
+          isRoot: function() {
+            return true;
+          },
+
+          scrollBy: function(x, y) {
+            let self = this.self;
+            self.contentWindow.scrollBy(x, y);
+          },
+
+          scrollTo: function(x, y) {
+            let self = this.self;
+            x = Math.floor(Math.max(0, Math.min(self.contentDocumentWidth,  x)));
+            y = Math.floor(Math.max(0, Math.min(self.contentDocumentHeight, y)));
+            self.contentWindow.scrollTo(x, y);
+          },
+
+          getPosition: function() {
+            let self = this.self;
+            let scrollX = {}, scrollY = {};
+            let cwu = self.contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+                      getInterface(Components.interfaces.nsIDOMWindowUtils);
+            cwu.getScrollXY(false, scrollX, scrollY);
+            return { x: scrollX.value, y: scrollY.value };
+          },
+
+          toString: function() {
+            return "[View Local]";
+          }
+        })
+        ]]>
+      </field>
+
+      <!-- Change client coordinates in device pixels to page-relative ones in CSS px. -->
+      <method name="transformClientToBrowser">
+        <parameter name="clientX"/>
+        <parameter name="clientY"/>
+        <body>
+          <![CDATA[
+            let bcr = this.getBoundingClientRect();
+            let scroll = this.getRootView().getPosition();
+            return { x: (clientX + scroll.x - bcr.left) / this.scale,
+                     y: (clientY + scroll.y - bcr.top) / this.scale };
+          ]]>
+        </body>
+      </method>
+
+      <method name="transformBrowserToClient">
+        <parameter name="browserX"/>
+        <parameter name="browserY"/>
+        <body>
+          <![CDATA[
+            let bcr = this.getBoundingClientRect();
+            let scroll = this.getRootView().getPosition();
+            return { x: (browserX * this.scale - scroll.x + bcr.left) ,
+                     y: (browserY * this.scale - scroll.y + bcr.top)};
+          ]]>
+        </body>
+      </method>
+
+      <constructor>
+        <![CDATA[
+          this._frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+          this._contentViewManager = this._frameLoader.QueryInterface(Components.interfaces.nsIContentViewManager);
+
+          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                              .getService(Components.interfaces.nsIPrefBranch);
+
+          this._cacheRatioWidth = Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioWidth") / 1000);
+          this._cacheRatioHeight = Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioHeight") / 1000);
+
+          if (this._contentViewPrototype)
+            this._contentViewPrototype.kDieTime = prefService.getIntPref("toolkit.browser.contentViewExpire");
+
+          this.messageManager.loadFrameScript("chrome://browser/content/bindings/browser.js", true);
+          this.messageManager.addMessageListener("DOMTitleChanged", this._messageListenerLocal);
+          this.messageManager.addMessageListener("DOMLinkAdded", this._messageListenerLocal);
+          this.messageManager.addMessageListener("pageshow", this._messageListenerLocal);
+          this.messageManager.addMessageListener("pagehide", this._messageListenerLocal);
+          this.messageManager.addMessageListener("DOMPopupBlocked", this._messageListenerLocal);
+          this.messageManager.addMessageListener("MozScrolledAreaChanged", this._messageListenerLocal);
+          this.messageManager.addMessageListener("Content:UpdateDisplayPort", this._messageListenerLocal);
+
+          this._webProgress._init();
+
+          // Remove event listeners added by toolkit <browser> binding.
+          this.removeEventListener("pageshow", this.onPageShow, true);
+          this.removeEventListener("pagehide", this.onPageHide, true);
+          this.removeEventListener("DOMPopupBlocked", this.onPopupBlocked, true);
+        ]]>
+      </constructor>
+
+      <method name="updateWindowId">
+         <parameter name="aNewId"/>
+         <body><![CDATA[
+            if (this.contentWindowId != aNewId) {
+               this.contentWindowId = aNewId;
+               return true;
+            }
+            return false;
+         ]]></body>
+      </method>
+
+      <field name="_active">false</field>
+      <property name="active" onget="return this._active;">
+        <setter><![CDATA[
+          // Do not change displayport on local tabs!
+          this._active = val;
+        ]]></setter>
+      </property>
+
+      <!-- Transform the viewport without updating the displayport. -->
+      <method name="fuzzyZoom">
+        <parameter name="scale"/>
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body><![CDATA[
+          this.getRootView().scrollTo(x, y);
+        ]]></body>
+      </method>
+
+      <!-- After fuzzy zoom, sync the displayport with the new viewport. -->
+      <method name="finishFuzzyZoom">
+        <body><![CDATA[
+          return;
+        ]]></body>
+      </method>
+
+    </implementation>
+  </binding>
+
+  <binding id="remote-browser" extends="#local-browser">
+    <implementation type="application/javascript" implements="nsIAccessibleProvider, nsIObserver, nsIDOMEventListener, nsIMessageListener">
+      <property name="accessibleType" readonly="true">
+        <getter>
+          <![CDATA[
+            throw "accessibleType: Supports Remote?";
+          ]]>
+        </getter>
+      </property>
+
+      <property name="autoscrollEnabled">
+        <getter>
+          <![CDATA[
+            throw "autoscrollEnabled: Supports Remote?";
+          ]]>
+        </getter>
+      </property>
+
+      <property name="docShell"
+                readonly="true">
+         <getter><![CDATA[
+            return {
+               forcedCharset : this._charset,
+               parentCharset : "",
+               parentCharsetSource : 0
+            }
+         ]]></getter>
+      </property>
+
+      <field name="_contentTitle">null</field>
+
+      <property name="contentTitle"
+                onget="return this._contentTitle;"
+                readonly="true"/>
+
+      <property name="webNavigation"
+                onget="return this._remoteWebNavigation;"
+                readonly="true"/>
+
+      <property name="contentWindow"
+                readonly="true"
+                onget="return null"/>
+
+      <property name="sessionHistory"
+                onget="return null"
+                readonly="true"/>
+
+      <property name="markupDocumentViewer"
+                onget="return null"
+                readonly="true"/>
+
+      <property name="contentViewerEdit"
+                onget="return null"
+                readonly="true"/>
+
+      <property name="contentViewerFile"
+                onget="return null"
+                readonly="true"/>
+
+      <field name="_charset"></field>
+
+      <constructor>
+        <![CDATA[
+          this.messageManager.addMessageListener("scroll", this._messageListenerRemote);
+        ]]>
+      </constructor>
+
+      <field name="scrollSync">true</field>
+
+      <field name="_messageListenerRemote"><![CDATA[
+        ({
+          self: this,
+          receiveMessage: function receiveMessage(aMessage) {
+            let self = this.self;
+            let json = aMessage.json;
+
+            switch (aMessage.name) {
+              case "scroll":
+                if (!self.scrollSync)
+                  return;
+                this.doScroll(json.x, json.y, 0);
+                break;
+           }
+         },
+
+         doScroll: function doScroll(aX, aY, aCount) {
+            let self = this.self;
+
+            // Use floor so that we always guarantee top-left corner of content is visible.
+            let view = self.getRootView();
+            view.scrollTo(Math.floor(aX * self.scale), Math.floor(aY * self.scale));
+
+            let position = view.getPosition();
+            if ((position.x != aX * self.scale || position.y != aY * self.scale) && aCount < 3) {
+              setTimeout((function() {
+                 this.doScroll(aX, aY, ++aCount);
+              }).bind(this), 0);
+            }
+         }
+       })
+      ]]></field>
+
+      <!-- Keep a store of temporary content views. -->
+      <field name="_contentViews">({})</field>
+
+      <!-- There is a point before a page has loaded where a root content view
+           may not exist. We use this so that we don't have to worry about doing
+           an if check every time we want to scroll. -->
+      <field name="_contentNoop"><![CDATA[
+        ({
+          _updateCacheViewport: function() {},
+          _getViewportSize: function() {},
+
+          isRoot: function() {
+            return true;
+          },
+
+          _scale: 1,
+          _setScale: function(scale) {},
+          scrollBy: function(x, y) {},
+          scrollTo: function(x, y) {},
+          getPosition: function() {
+            return { x: 0, y: 0 };
+          }
+        })
+      ]]></field>
+
+      <field name="_contentViewPrototype"><![CDATA[
+        ({
+          self: this,
+          _id: null,
+          _contentView: null,
+          _timeout: null,
+          _pixelsPannedSinceRefresh: { x: 0, y: 0 },
+          _lastPanTime: 0,
+
+          kDieTime: 3000,
+
+          /** 
+           * Die if we haven't panned in a while.
+           *
+           * Since we keep a map of active content views, we need to regularly
+           * check if they are necessary so that every single thing the user
+           * pans is not kept in memory forever.
+           */
+          _dieIfOld: function() {
+            if (Date.now() - this._lastPanTime >= this.kDieTime)
+              this._die();
+            else
+              // This doesn't need to be exact, just be sure to clean up at some point.
+              this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
+          },
+
+          /** Cleanup after ourselves. */
+          _die: function() {
+            let timeout = this._timeout;
+            if (timeout) {
+              clearTimeout(timeout);
+              this._timeout = null;
+            }
+
+            if (this._contentView && Math.abs(this._pixelsPannedSinceRefresh) > 0)
+              this._updateCacheViewport();
+
+            // We expect contentViews to contain our ID. If not, something bad
+            // happened.
+            delete this.self._contentViews[this._id];
+          },
+
+          /**
+           * Given the cache size and the viewport size, this determines where the cache
+           * should start relative to the scroll position. This adjusts the position based
+           * on which direction the user is panning, so that we use our cache as
+           * effectively as possible.
+           *
+           * @param aDirection Negative means user is panning to the left or above
+           *                   Zero means user did not pan
+           *                   Positive means user is panning to the right or below
+           * @param aViewportSize The width or height of the viewport
+           * @param aCacheSize The width or height of the displayport
+           */
+          _getRelativeCacheStart: function(aDirection, aViewportSize, aCacheSize) {
+            // Remember that this is relative to the viewport scroll position.
+            // Let's assume we are thinking about the y-axis.
+            // The extreme cases:
+            // |0| would mean that there is no content available above
+            // |aViewportSize - aCacheSize| would mean no content available below
+            //
+            // Taking the average of the extremes puts equal amounts of cache on the
+            // top and bottom of the viewport. If we think of this like a weighted
+            // average, .5 is the sweet spot where equals amounts of content are
+            // above and below the visible area.
+            //
+            // This weight is therefore how much of the cache is above (or to the
+            // left) the visible area.
+            let cachedAbove = .5;
+
+            // If panning down, leave only 25% of the non-visible cache above.
+            if (aDirection > 0)
+              cachedAbove = .25;
+
+            // If panning up, Leave 75% of the non-visible cache above.
+            if (aDirection < 0)
+              cachedAbove = .75;
+
+            return (aViewportSize - aCacheSize) * cachedAbove;
+          },
+
+          /** Determine size of the pixel cache. */
+          _getCacheSize: function(viewportSize) {
+            let self = this.self;
+            let contentView = this._contentView;
+
+            let cacheWidth = self._cacheRatioWidth * viewportSize.width;
+            let cacheHeight = self._cacheRatioHeight * viewportSize.height;
+            let contentSize = this._getContentSize();
+            let contentWidth = contentSize.width;
+            let contentHeight = contentSize.height;
+
+            // There are common cases, such as long skinny pages, where our cache size is
+            // bigger than our content size. In those cases, we take that sliver of leftover
+            // space and apply it to the other dimension.
+            if (contentWidth < cacheWidth) {
+              cacheHeight += (cacheWidth - contentWidth) * cacheHeight / cacheWidth;
+              cacheWidth = contentWidth;
+            } else if (contentHeight < cacheHeight) {
+              cacheWidth += (cacheHeight - contentHeight) * cacheWidth / cacheHeight;
+              cacheHeight = contentHeight;
+            }
+
+            return { width: cacheWidth, height: cacheHeight };
+          },
+
+          _sendDisplayportUpdate: function(scrollX, scrollY) {
+            let self = this.self;
+            if (!self.active)
+               return;
+
+            let contentView = this._contentView;
+            let viewportSize = this._getViewportSize();
+            let cacheSize = this._getCacheSize(viewportSize);
+            let cacheX = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.x, viewportSize.width, cacheSize.width) + contentView.scrollX;
+            let cacheY = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.y, viewportSize.height, cacheSize.height) + contentView.scrollY;
+            let contentSize = this._getContentSize();
+
+            // Use our pixels efficiently and don't try to cache things outside of content
+            // boundaries (The left bound can be negative because of RTL).
+
+            let rootScale = self.scale;
+            let leftBound = self._contentDocumentLeft * rootScale;
+            let bounds = new Rect(leftBound, 0, contentSize.width, contentSize.height);
+            let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height);
+            displayport.translateInside(bounds);
+
+            self.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
+              scrollX: Math.round(scrollX) / rootScale,
+              scrollY: Math.round(scrollY) / rootScale,
+              x: Math.round(displayport.x) / rootScale,
+              y: Math.round(displayport.y) / rootScale,
+              w: Math.round(displayport.width) / rootScale,
+              h: Math.round(displayport.height) / rootScale,
+              scale: rootScale,
+              id: contentView.id
+            });
+
+            this._pixelsPannedSinceRefresh.x = 0;
+            this._pixelsPannedSinceRefresh.y = 0;
+          },
+
+          _updateCSSViewport: function() {
+            let contentView = this._contentView;
+            this._sendDisplayportUpdate(contentView.scrollX,
+                                        contentView.scrollY);
+          },
+
+          /**
+           * The cache viewport is what parts of content is cached in the parent process for
+           * fast scrolling. This syncs that up with the current projection viewport.
+           */
+          _updateCacheViewport: function() {
+            // Do not update scroll values for content.
+            if (this.isRoot())
+              this._sendDisplayportUpdate(-1, -1);
+            else {
+              let contentView = this._contentView;
+              this._sendDisplayportUpdate(contentView.scrollX,
+                                          contentView.scrollY);
+            }
+          },
+
+          _getContentSize: function() {
+            let self = this.self;
+            return { width: this._contentView.contentWidth,
+                     height: this._contentView.contentHeight };
+          },
+
+          _getViewportSize: function() {
+            let self = this.self;
+            if (this.isRoot()) {
+              let bcr = self.getBoundingClientRect();
+              return { width: bcr.width, height: bcr.height };
+            } else {
+              return { width: this._contentView.viewportWidth,
+                       height: this._contentView.viewportHeight };
+            }
+          },
+
+          init: function(contentView) {
+            let self = this.self;
+
+            this._contentView = contentView;
+            this._id = contentView.id;
+            this._scale = 1;
+            self._contentViews[this._id] = this;
+
+            if (!this.isRoot()) {
+              // Non-root content views are short lived.
+              this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
+              // This iframe may not have a display port yet, so build up a cache
+              // immediately.
+              this._updateCacheViewport();
+            }
+          },
+
+          isRoot: function() {
+            return this.self._contentViewManager.rootContentView == this._contentView;
+          },
+
+          scrollBy: function(x, y) {
+            let self = this.self;
+
+            // Bounding content rectangle is in device pixels
+            let contentView = this._contentView;
+            let viewportSize = this._getViewportSize();
+            let contentSize = this._getContentSize();
+            // Calculate document dimensions in device pixels
+            let scrollRangeX = contentSize.width - viewportSize.width;
+            let scrollRangeY = contentSize.height - viewportSize.height;
+
+            let leftOffset = self._contentDocumentLeft * this._scale;
+            x = Math.floor(Math.max(leftOffset, Math.min(scrollRangeX + leftOffset, contentView.scrollX + x))) - contentView.scrollX;
+            y = Math.floor(Math.max(0, Math.min(scrollRangeY, contentView.scrollY + y))) - contentView.scrollY;
+
+            if (x == 0 && y == 0)
+              return;
+
+            contentView.scrollBy(x, y);
+
+            this._lastPanTime = Date.now();
+
+            this._pixelsPannedSinceRefresh.x += x;
+            this._pixelsPannedSinceRefresh.y += y;
+            if (Math.abs(this._pixelsPannedSinceRefresh.x) > 20 ||
+                Math.abs(this._pixelsPannedSinceRefresh.y) > 20)
+              this._updateCacheViewport();
+          },
+
+          scrollTo: function(x, y) {
+            let contentView = this._contentView;
+            this.scrollBy(x - contentView.scrollX, y - contentView.scrollY);
+          },
+
+          _setScale: function _setScale(scale) {
+            this._scale = scale;
+            this._contentView.setScale(scale, scale);
+          },
+
+          getPosition: function() {
+            let contentView = this._contentView;
+            return { x: contentView.scrollX, y: contentView.scrollY };
+          }
+        })
+        ]]>
+      </field>
+
+      <!-- Transform the viewport without updating the displayport. -->
+      <method name="fuzzyZoom">
+        <parameter name="scale"/>
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body><![CDATA[
+          let rootView = this.getRootView();
+          rootView._setScale(scale);
+          if ("_contentView" in rootView)
+            rootView._contentView.scrollTo(x, y);
+        ]]></body>
+      </method>
+
+      <!-- After fuzzy zoom, sync the displayport with the new viewport. -->
+      <method name="finishFuzzyZoom">
+        <body><![CDATA[
+          let view = this.getRootView();
+
+          // ensure that we are scrolled within the page's viewable area
+          view.scrollBy(0,0);
+          view._updateCacheViewport();
+
+          let event = document.createEvent("Events");
+          event.initEvent("ZoomChanged", true, false);
+          this.dispatchEvent(event);
+        ]]></body>
+      </method>
+
+      <!-- The ratio of CSS pixels to device pixels. -->
+      <property name="scale">
+        <getter><![CDATA[
+          return this.getRootView()._scale;
+        ]]></getter>
+        <setter><![CDATA[
+          if (val <= 0 || val == this.scale)
+            return;
+
+          let rootView = this.getRootView();
+          rootView._setScale(val);
+          this.finishFuzzyZoom();
+
+          return val;
+        ]]></setter>
+      </property>
+
+      <method name="_getView">
+        <parameter name="contentView"/>
+        <body>
+          <![CDATA[
+            if (!contentView) return null;
+
+            // See if we have cached it.
+            let id = contentView.id;
+            let jsContentView = this._contentViews[id];
+            if (jsContentView) {
+              // Content view may have changed if it became inactive for a
+              // little while.
+              jsContentView._contentView = contentView;
+              return jsContentView;
+            }
+
+            // Not cached. Create it.
+            jsContentView = Object.create(this._contentViewPrototype);
+            jsContentView.init(contentView);
+            return jsContentView;
+          ]]>
+        </body>
+      </method>
+
+      <!-- Get root content view. -->
+      <method name="getRootView">
+        <body>
+          <![CDATA[
+            let contentView = this._contentViewManager.rootContentView;
+            return this._getView(contentView) || this._contentNoop;
+          ]]>
+        </body>
+      </method>
+
+      <!-- Get contentView for position (x, y) relative to the browser element -->
+      <method name="getViewAt">
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body>
+          <![CDATA[
+            let manager = this._contentViewManager;
+            let contentView = manager.getContentViewsIn(x, y, 0, 0, 0, 0)[0] ||
+                              manager.rootContentView;
+            return this._getView(contentView);
+          ]]>
+        </body>
+      </method>
+
+      <!-- Synchronize the CSS viewport with the projection viewport. -->
+      <method name="_updateCSSViewport">
+        <body>
+          <![CDATA[
+            let rootView = this.getRootView();
+            rootView._updateCSSViewport();
+          ]]>
+        </body>
+      </method>
+
+      <property name="active" onget="return this._active;">
+        <setter><![CDATA[
+            this._active = val;
+            let keepVisible = false;
+            this.messageManager.sendAsyncMessage((val ? "Content:Activate" : "Content:Deactivate"), { keepviewport: keepVisible });
+            if (val)
+              this.getRootView()._updateCacheViewport();
+         ]]></setter>
+      </property>
+
+    </implementation>
+
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/console.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="error" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
+    <content orient="vertical">
+      <xul:hbox class="console-row-internal-box" flex="1">
+        <xul:vbox class="console-row-content" flex="1">
+          <xul:hbox class="console-row-msg" align="start">
+            <xul:label class="label title" xbl:inherits="value=typetext"/>
+            <xul:description class="console-error-msg title" xbl:inherits="xbl:text=msg" flex="1"/>
+          </xul:hbox>
+          
+          <xul:hbox class="console-row-file" xbl:inherits="hidden=hideSource">
+            <xul:label class="label title" value="&consoleErrFile.label;"/>
+            <xul:label class="title" xbl:inherits="value=href" crop="right"/>
+            <xul:spacer flex="1"/>
+            <xul:hbox class="lineNumberRow" xbl:inherits="line">
+              <xul:label class="label title" value="&consoleErrLine.label;"/>
+              <xul:label class="label title" xbl:inherits="value=line"/>
+            </xul:hbox>
+          </xul:hbox>
+          
+          <xul:vbox class="console-row-code" xbl:inherits="hidden=hideCode">
+            <xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
+            <xul:hbox xbl:inherits="hidden=hideCaret">
+              <xul:label class="monospace console-dots title" xbl:inherits="value=errorDots"/>
+              <xul:label class="monospace console-caret title" xbl:inherits="value=errorCaret"/>
+              <xul:spacer flex="1"/>
+            </xul:hbox>
+          </xul:vbox>
+        </xul:vbox>
+      </xul:hbox>
+    </content>
+  </binding>
+    
+  <binding id="message" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
+    <content>
+      <xul:hbox class="console-internal-box" flex="1">
+        <xul:vbox class="console-row-content" flex="1">
+          <xul:vbox class="console-row-msg" flex="1">
+            <xul:description class="console-msg-text title" xbl:inherits="xbl:text=msg"/>
+          </xul:vbox>
+        </xul:vbox>
+      </xul:hbox>
+    </content>
+  </binding>
+  
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/dialog.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="dialog">
+    <content xbl:inherits="orient, closebutton" flex="1">
+      <children/>
+    </content>
+
+    <implementation implements="nsIDOMEventListener">
+      <field name="arguments"/>
+      <field name="parent"/>
+      <property name="_scrollbox" readonly="true" onget="return this.getElementsByTagName('scrollbox')[0];"/>
+
+      <constructor><![CDATA[
+        this._closed = false;
+        if (this.hasAttribute("script")) {
+          try {
+            Services.scriptloader.loadSubScript(this.getAttribute("script"), this);
+          } catch(e) {
+            throw("Dialog : Unable to load script : " + this.getAttribute("script") + "\n");
+          }
+        }
+        window.addEventListener("unload", this, true);
+
+        let scrollbox = this._scrollbox;
+        if (scrollbox) {
+          window.addEventListener("resize", this, true);
+          scrollbox.addEventListener("overflow", this, true);
+        }
+        setTimeout(this.load.bind(this), 0);
+      ]]></constructor>
+
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body><![CDATA[
+          switch(aEvent.type) {
+            case "unload":
+              if (aEvent.originalTarget == document)
+                this._removeDialog();
+              break;
+
+            case "resize":
+            case "overflow":
+              let scrollbox = this._scrollbox;
+              let style = document.defaultView.getComputedStyle(scrollbox, null);
+              let newHeight =  Math.ceil(scrollbox.firstChild.getBoundingClientRect().height) + 
+                               parseInt(style.marginTop) +
+                               parseInt(style.marginBottom);
+              scrollbox.style.minHeight = Math.min(window.innerHeight / 2, newHeight) + "px";
+              break;
+          }
+        ]]></body>
+      </method>
+
+      <method name="load">
+        <body><![CDATA[
+          if (this.hasAttribute("onload")) {
+            let func = new Function(this.getAttribute("onload"));
+            func.call(this);
+          }
+        ]]></body>
+      </method>
+
+      <method name="close">
+        <body><![CDATA[
+          if (this.hasAttribute("onclose")) {
+            let func = new Function(this.getAttribute("onclose"));
+            func.call(this);
+          }
+          this._removeDialog();
+        ]]></body>
+      </method>
+
+      <method name="_removeDialog">
+        <body><![CDATA[
+          window.removeEventListener("unload", this, true);
+          let scrollbox = this._scrollbox;
+          if (scrollbox) {
+            window.removeEventListener("resize", this, true);
+            scrollbox.removeEventListener("overflow", this, true);
+          }
+
+          this.parentNode.parentNode.removeChild(this.parentNode);
+          this._closed = true;
+
+          // emit DOMModalDialogClosed event
+          let event = document.createEvent("Events");
+          event.initEvent("DOMModalDialogClosed", true, false);
+          let dispatcher = this.parent || getBrowser();
+          dispatcher.dispatchEvent(event);
+        ]]></body>
+      </method>
+      
+      <method name="waitForClose">
+        <body><![CDATA[
+          while (!this._closed)
+            Services.tm.currentThread.processNextEvent(true);
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/downloads.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="download-base" extends="chrome://browser/content/bindings/grid.xml#richgrid-item">
+    <content>
+      <xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox class="richgrid-icon-container">
+          <xul:box class="richgrid-icon-box"><xul:image xbl:inherits="src=iconURI"/></xul:box>
+          <xul:box flex="1" />
+        </xul:hbox>
+        <xul:description xbl:inherits="value=label" crop="end"/>
+      </xul:vbox>
+      <children/>
+    </content>
+    <implementation>
+      <field name="nsIDLMgr">Components.interfaces.nsIDownloadManager</field>
+
+      <property name="paused">
+        <getter>
+        <![CDATA[
+          return parseInt(this.getAttribute("state")) == this.nsIDLMgr.DOWNLOAD_PAUSED;
+        ]]>
+        </getter>
+      </property>
+      <property name="openable">
+        <getter>
+        <![CDATA[
+          return parseInt(this.getAttribute("state")) == this.nsIDLMgr.DOWNLOAD_FINISHED;
+        ]]>
+        </getter>
+      </property>
+      <property name="inProgress">
+        <getter>
+        <![CDATA[
+          var state = parseInt(this.getAttribute("state"));
+          return state == this.nsIDLMgr.DOWNLOAD_NOTSTARTED ||
+                 state == this.nsIDLMgr.DOWNLOAD_QUEUED ||
+                 state == this.nsIDLMgr.DOWNLOAD_DOWNLOADING ||
+                 state == this.nsIDLMgr.DOWNLOAD_PAUSED ||
+                 state == this.nsIDLMgr.DOWNLOAD_SCANNING;
+        ]]>
+        </getter>
+      </property>
+      <property name="removable">
+        <getter>
+        <![CDATA[
+          var state = parseInt(this.getAttribute("state"));
+          return state == this.nsIDLMgr.DOWNLOAD_FINISHED ||
+                 state == this.nsIDLMgr.DOWNLOAD_CANCELED ||
+                 state == this.nsIDLMgr.DOWNLOAD_BLOCKED_PARENTAL ||
+                 state == this.nsIDLMgr.DOWNLOAD_BLOCKED_POLICY ||
+                 state == this.nsIDLMgr.DOWNLOAD_DIRTY ||
+                 state == this.nsIDLMgr.DOWNLOAD_FAILED;
+        ]]>
+        </getter>
+      </property>
+    </implementation>
+  </binding>
+
+  <binding id="download-not-started" extends="#download-base">
+   <content orient="horizontal" align="start">
+      <xul:image validate="always" xbl:inherits="src=iconURL"/>
+      <xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox align="center">
+          <xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
+          <xul:label class="normal" xbl:inherits="value=datetime"/>
+        </xul:hbox>
+        <xul:hbox>
+          <xul:label class="normal" xbl:inherits="value=status"/>
+        </xul:hbox>
+        <xul:hbox class="show-on-select" align="center">
+          <xul:button anonid="showpage-button" label="&downloadShowPage.label;"
+                      oncommand="Downloads.showPage(document.getBindingParent(this));"/>
+          <xul:spacer flex="1"/>
+        </xul:hbox>
+      </xul:vbox>
+    </content>
+
+    <implementation>
+      <constructor>
+        <![CDATA[
+          let referrer = this.hasAttribute("referrer");
+          if (!referrer)
+            document.getAnonymousElementByAttribute(this, "anonid", "showpage-button").setAttribute("disabled", "true");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+
+
+  <binding id="download-downloading" extends="#download-base">
+   <content orient="horizontal" align="start">
+      <xul:image validate="always" xbl:inherits="src=iconURL"/>
+      <xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox align="center">
+          <xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
+          <xul:label class="normal" xbl:inherits="value=datetime"/>
+        </xul:hbox>
+        <xul:hbox align="center">
+          <xul:progressmeter anonid="progressmeter" mode="normal" value="0" flex="1" xbl:inherits="value=progress,mode=progressmode"/>
+          <xul:button class="download-pause" label="&downloadPause.label;"
+                      oncommand="Downloads.pauseDownload(document.getBindingParent(this));"/>
+          <xul:button class="download-cancel" label="&downloadCancel.label;"
+                      oncommand="Downloads.cancelDownload(document.getBindingParent(this));"/>
+        </xul:hbox>
+        <xul:label class="normal" xbl:inherits="value=status" crop="end"/>
+      </xul:vbox>
+    </content>
+  </binding>
+
+  <binding id="download-paused" extends="#download-base">
+   <content orient="horizontal" align="start">
+      <xul:image validate="always" xbl:inherits="src=iconURL"/>
+      <xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox align="center">
+          <xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
+          <xul:label class="normal" xbl:inherits="value=datetime"/>
+        </xul:hbox>
+        <xul:hbox align="center">
+          <xul:progressmeter anonid="progressmeter" mode="normal" value="0" flex="1" xbl:inherits="value=progress,mode=progressmode"/>
+          <xul:button class="download-resume" label="&downloadResume.label;"
+                      oncommand="Downloads.resumeDownload(document.getBindingParent(this));"/>
+          <xul:button class="download-cancel" label="&downloadCancel.label;"
+                      oncommand="Downloads.cancelDownload(document.getBindingParent(this));"/>
+        </xul:hbox>
+        <xul:label class="normal" xbl:inherits="value=status" crop="end"/>
+      </xul:vbox>
+    </content>
+  </binding>
+
+  <binding id="download-retry" extends="#download-base">
+   <content orient="horizontal" align="start">
+      <xul:image validate="always" xbl:inherits="src=iconURL"/>
+      <xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox align="center">
+          <xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
+          <xul:label class="normal" xbl:inherits="value=datetime"/>
+        </xul:hbox>
+        <xul:hbox>
+          <xul:label class="normal" xbl:inherits="value=status" crop="end" flex="1"/>
+          <xul:label class="hide-on-select download-retry-failed normal" value="&downloadFailed.label;" xbl:inherits="state" />
+          <xul:button class="show-on-select download-retry" label="&downloadRetry.label;"
+                      oncommand="Downloads.retryDownload(document.getBindingParent(this));"/>
+          <xul:button class="show-on-select download-remove" label="&downloadRemove.label;"
+                      oncommand="Downloads.removeDownload(document.getBindingParent(this));"/>
+        </xul:hbox>
+      </xul:vbox>
+    </content>
+  </binding>
+
+  <binding id="download-done" extends="#download-base">
+   <content orient="horizontal" align="start">
+      <xul:image validate="always" xbl:inherits="src=iconURL"/>
+      <xul:vbox flex="1" anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox align="center">
+          <xul:label class="title" xbl:inherits="value=name" crop="center" flex="1"/>
+          <xul:label class="normal" xbl:inherits="value=datetime"/>
+        </xul:hbox>
+        <xul:hbox>
+          <xul:label class="normal" xbl:inherits="value=status"/>
+        </xul:hbox>
+        <xul:hbox class="show-on-select" align="center">
+          <xul:button anonid="showpage-button" label="&downloadShowPage.label;"
+                      oncommand="Downloads.showPage(document.getBindingParent(this));"/>
+          <xul:spacer flex="1"/>
+          <xul:button anonid="open-button" label="&downloadOpen2.label;"
+                      oncommand="Downloads.openDownload(document.getBindingParent(this));"/>
+          <xul:button anonid="remove-button" label="&downloadRemove.label;"
+                      oncommand="Downloads.removeDownload(document.getBindingParent(this));"/>
+        </xul:hbox>
+      </xul:vbox>
+    </content>
+
+    <implementation>
+      <constructor>
+        <![CDATA[
+          let referrer = this.hasAttribute("referrer");
+          if (!referrer)
+            document.getAnonymousElementByAttribute(this, "anonid", "showpage-button").setAttribute("disabled", "true");
+          let file = Downloads._getLocalFile(this.getAttribute("target"));
+
+          let mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
+          let mimeType;
+          try {
+            mimeType = mimeService.getTypeFromFile(file);
+          }
+          catch(e) {}
+          if (!file.exists() || !mimeType)
+            document.getAnonymousElementByAttribute(this, "anonid", "open-button").setAttribute("disabled", "true");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/flyoutpanel.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="flyoutpanelBinding" extends="xul:box">
+    <resources>
+      <stylesheet src="chrome://browser/skin/flyoutpanel.css"/>
+    </resources>
+
+    <content>
+      <xul:vbox class="flyoutpanel-wrapper">
+        <xul:hbox class="flyoutpanel-header" align="top">
+          <xul:toolbarbutton class="flyout-close-button"
+                             command="cmd_flyout_back"
+                             xbl:inherits="command"/>
+          <xul:label class="flyout-header-label" xbl:inherits="value=headertext"/>
+        </xul:hbox>
+        <xul:scrollbox class="flyoutpanel-contents" flex="1" orient="vertical">
+          <children/>
+        </xul:scrollbox>
+      </xul:vbox>
+    </content>
+
+    <implementation implements="nsIDOMEventListener">
+      <constructor>
+        <![CDATA[
+          window.addEventListener('MozContextUIShow', this);
+          window.addEventListener('MozContextUIDismiss', this);
+          window.addEventListener('MozAppbarDismiss', this);
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+          window.removeEventListener('MozContextUIShow', this);
+          window.removeEventListener('MozContextUIDismiss', this);
+          window.removeEventListener('MozAppbarDismiss', this);
+        ]]>
+      </destructor>
+
+      <property name="isVisible" readonly="true">
+        <getter>
+          <![CDATA[
+            return this.hasAttribute("visible");
+          ]]>
+        </getter>
+      </property>
+      
+      <method name="hide">
+        <body>
+          <![CDATA[
+            this.removeAttribute("visible");
+            DialogUI.popPopup(this);
+          ]]>
+        </body>
+      </method>
+
+      <method name="show">
+        <body>
+          <![CDATA[
+            this.setAttribute("visible", "true");
+            DialogUI.pushPopup(this, this);
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            switch (aEvent.type) {
+              case 'MozContextUIShow':
+              case 'MozAppbarDismiss':
+              case 'MozContextUIDismiss':
+                this.hide();
+                break;
+            }
+          ]]>
+        </body>
+      </method>
+
+      <field name="anonScrollBox" readonly="true"><![CDATA[
+        // Expose the anyonymous scrollbox so ScrollUtils.getScrollboxFromElement can find it.
+        document.getAnonymousElementByAttribute(this, "class", "flyoutpanel-contents");
+      ]]></field>
+    </implementation>
+
+    <handlers>
+      <!-- Work around for bug 835175 -->
+      <handler event="click">false;</handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -0,0 +1,556 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:html="http://www.w3.org/1999/xhtml"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="richgrid"
+           extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+    <content>
+      <html:div id="grid-div" anonid="grid" class="richgrid-grid">
+        <children/>
+      </html:div>
+    </content>
+
+    <implementation implements="nsIDOMXULSelectControlElement">
+      <property name="_grid" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'grid');"/>
+      <field name="controller">null</field>
+
+      <!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
+
+      <method name="clearSelection">
+        <body>
+          <![CDATA[
+            // 'selection' and 'selected' are confusingly overloaded here
+            // as richgrid is adopting multi-select behavior, but select/selected are already being
+            // used to describe triggering the default action of a tile
+            if (this._selectedItem){
+              this._selectedItem.selected = false;
+              this._selectedItem = null;
+            }
+
+            for (let childItem of this.selectedItems) {
+              childItem.selected = false;
+            }
+            // reset context actions
+            this._contextActions = null;
+          ]]>
+        </body>
+      </method>
+
+      <method name="toggleItemSelection">
+        <parameter name="anItem"/>
+        <body>
+          <![CDATA[
+            anItem.selected = !anItem.selected;
+            this._fireOnSelectionChange();
+          ]]>
+        </body>
+      </method>
+
+      <method name="selectItem">
+        <parameter name="anItem"/>
+        <body>
+          <![CDATA[
+            this.clearSelection();
+            this._selectedItem = anItem;
+            this._selectedItem.selected = true;
+
+            this._fireOnSelect();
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleItemClick">
+        <parameter name="aItem"/>
+        <body>
+          <![CDATA[
+            if (this.controller)
+              this.controller.handleItemClick(aItem);
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleItemContextMenu">
+        <parameter name="aItem"/>
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            // we'll republish this as a selectionchange event on the grid
+            aEvent.stopPropagation();
+            this.toggleItemSelection(aItem);
+          ]]>
+        </body>
+      </method>
+
+      <field name="_contextActions">null</field>
+      <property name="contextActions">
+        <getter>
+          <![CDATA[
+            // return the subset of verbs that apply to all selected tiles
+            // use cached list, it'll get cleared out when selection changes
+            if (this._contextActions) {
+              return this._contextActions;
+            }
+            let tileNodes = this.selectedItems;
+            if (!tileNodes.length) {
+              return new Set();
+            }
+
+            function cloneSet(aSet) {
+              let clone = new Set();
+              if (aSet) {
+                for (let item of aSet) {
+                  clone.add(item);
+                }
+              }
+              return clone;
+            }
+
+            // given one or more sets of values,
+            // return a set with only those values present in each
+            let initialItem = tileNodes[0];
+
+            let verbSet = cloneSet(initialItem.contextActions);
+            for (let i=1; i<tileNodes.length; i++){
+              let set = tileNodes[i].contextActions;
+              for (let item of verbSet) {
+                if (!set.has(item)){
+                  verbSet.delete(item);
+                }
+              }
+            }
+
+            this._contextActions = verbSet;
+            // returns Set
+            return this._contextActions;
+          ]]>
+        </getter>
+      </property>
+
+    <!-- nsIDOMXULSelectControlElement -->
+
+      <property name="itemCount" readonly="true" onget="return this.children.length;"/>
+
+      <field name="_selectedItem">null</field>
+      <property name="selectedItem" onget="return this._selectedItem;">
+        <setter>
+          <![CDATA[
+            this.selectItem(val);
+          ]]>
+        </setter>
+      </property>
+
+      <!-- partial implementation of multiple selection interface -->
+      <property name="selectedItems">
+        <getter>
+          <![CDATA[
+            return this.querySelectorAll("richgriditem[selected='true']");
+          ]]>
+        </getter>
+      </property>
+
+      <property name="selectedIndex">
+        <getter>
+          <![CDATA[
+            return this.getIndexOfItem(this._selectedItem);
+          ]]>
+        </getter>
+        <setter>
+          <![CDATA[
+            if (val >= 0) {
+              let selected = this.getItemAtIndex(val);
+              this.selectItem(selected);
+            } else {
+              this.clearSelection();
+            }
+          ]]>
+        </setter>
+      </property>
+
+      <method name="appendItem">
+        <parameter name="aLabel"/>
+        <parameter name="aValue"/>
+        <body>
+          <![CDATA[
+            let addition = this._createItemElement(aLabel, aValue);
+            this.appendChild(addition);
+            this.arrangeItems();
+            return addition;
+          ]]>
+        </body>
+      </method>
+
+      <method name="insertItemAt">
+        <parameter name="anIndex"/>
+        <parameter name="aLabel"/>
+        <parameter name="aValue"/>
+        <body>
+          <![CDATA[
+            let existing = this.getItemAtIndex(anIndex);
+            let addition = this._createItemElement(aLabel, aValue);
+            if (existing) {
+              this.insertBefore(addition, existing);
+            } else {
+              this.appendChild(addition);
+            }
+            this.arrangeItems();
+            return addition;
+          ]]>
+        </body>
+      </method>
+
+      <method name="removeItemAt">
+        <parameter name="anIndex"/>
+        <body>
+          <![CDATA[
+            let removal = this.getItemAtIndex(anIndex);
+            this.removeChild(removal);
+            this.arrangeItems();
+            return removal;
+          ]]>
+        </body>
+      </method>
+
+      <method name="getIndexOfItem">
+        <parameter name="anItem"/>
+        <body>
+          <![CDATA[
+            if (!anItem)
+              return -1;
+
+            return Array.prototype.indexOf.call(this.children, anItem);
+          ]]>
+        </body>
+      </method>
+
+      <method name="getItemAtIndex">
+        <parameter name="anIndex"/>
+        <body>
+          <![CDATA[
+            if (!this._isIndexInBounds(anIndex))
+              return null;
+            return this.children.item(anIndex);
+          ]]>
+        </body>
+      </method>
+
+    <!-- Interface for offsetting selection and checking bounds -->
+
+      <property name="isSelectionAtStart" readonly="true"
+                onget="return this.selectedIndex == 0;"/>
+
+      <property name="isSelectionAtEnd" readonly="true"
+                onget="return this.selectedIndex == (this.itemCount - 1);"/>
+
+      <property name="isSelectionInStartRow" readonly="true">
+        <getter>
+          <![CDATA[
+            return this.selectedIndex < this.columnCount;
+          ]]>
+        </getter>
+      </property>
+
+      <property name="isSelectionInEndRow" readonly="true">
+        <getter>
+          <![CDATA[
+            let lowerBound = (this.rowCount - 1) * this.columnCount;
+            let higherBound = this.rowCount * this.columnCount;
+
+            return this.selectedIndex >= lowerBound &&
+                   this.selectedIndex < higherBound;
+          ]]>
+        </getter>
+      </property>
+
+      <method name="offsetSelection">
+        <parameter name="aOffset"/>
+        <body>
+          <![CDATA[
+            let newIndex = this.selectedIndex + aOffset;
+            if (this._isIndexInBounds(newIndex))
+              this.selectedIndex = newIndex;
+          ]]>
+        </body>
+      </method>
+
+      <method name="offsetSelectionByRow">
+        <parameter name="aRowOffset"/>
+        <body>
+          <![CDATA[
+            let newIndex = this.selectedIndex + (this.columnCount * aRowOffset);
+            if (this._isIndexInBounds(newIndex))
+              this.selectedIndex -= this.columnCount;
+          ]]>
+        </body>
+      </method>
+
+      <!-- Interface for grid layout management -->
+
+      <field name="_rowCount">0</field>
+      <property name="rowCount" readonly="true" onget="return this._rowCount;"/>
+
+      <field name="_columnCount">0</field>
+      <property name="columnCount" readonly="true" onget="return this._columnCount;"/>
+
+      <field name="_scheduledArrangeItemsTries">0</field>
+
+      <!--  define a height where we consider an item not yet rendered
+            10 is the height of the empty item (padding/border etc. only) -->
+      <field name="_itemHeightRenderThreshold">10</field>
+
+      <method name="arrangeItems">
+        <parameter name="aNumRows"/>
+        <parameter name="aNumColumns"/>
+        <body>
+          <![CDATA[
+            if (this.itemCount <= 0)
+              return;
+            let item = this.getItemAtIndex(0);
+            if (item == null)
+              return;
+            let gridItemRect = item.getBoundingClientRect();
+
+            // cap the number of times we reschedule calling arrangeItems
+            let maxRetries = 5;
+
+            // delay as necessary until the item has a proper height
+            if (gridItemRect.height <= this._itemHeightRenderThreshold) {
+              if (this._scheduledArrangeItemsTimerId) {
+                // retry of arrangeItems already scheduled
+                return;
+              }
+
+              // track how many times we've attempted arrangeItems
+              this._scheduledArrangeItemsTries++;
+
+              if (maxRetries > this._scheduledArrangeItemsTries) {
+                // schedule re-try of arrangeItems at the next tick
+                this._scheduledArrangeItemsTimerId = setTimeout(this.arrangeItems.bind(this), 0);
+                return;
+              }
+            }
+
+            // items ready to arrange (or retries max exceeded)
+            // reset the flags
+            if (this._scheduledArrangeItemsTimerId) {
+              clearTimeout(this._scheduledArrangeItemsTimerId);
+              delete this._scheduledArrangeItemsTimerId;
+            }
+            if (this._scheduledArrangeItemsTries) {
+              this._scheduledArrangeItemsTries = 0;
+            }
+
+            // Autocomplete is a binding within a binding, so we have to step
+            // up an additional parentNode.
+            let container = null;
+            if (this.parentNode.id == "results-vbox" ||
+                this.parentNode.id == "searches-vbox")
+              container = this.parentNode.parentNode.getBoundingClientRect();
+            else
+              container = this.parentNode.getBoundingClientRect();
+
+            // If we don't have valid dimensions we can't arrange yet
+            if (!container.height || !gridItemRect.height)
+              return;
+
+            // We favor overflowing horizontally, not vertically
+            let maxRowCount = Math.floor(container.height / gridItemRect.height) - 1;
+
+            if (aNumRows) {
+              this._rowCount = aNumRows;
+            } else {
+              this._rowCount = Math.min(this.itemCount, maxRowCount);
+            }
+            if (aNumColumns) {
+              this._columnCount = aNumColumns;
+            } else {
+              this._columnCount = Math.ceil(this.itemCount / this._rowCount);
+            }
+
+            this._grid.style.width = (this._columnCount * gridItemRect.width) + "px";
+          ]]>
+        </body>
+      </method>
+
+      <!-- Inteface to suppress selection events -->
+
+      <field name="_suppressOnSelect"/>
+      <property name="suppressOnSelect"
+                  onget="return this.getAttribute('suppressonselect') == 'true';"
+                  onset="this.setAttribute('suppressonselect', val);"/>
+
+    <!-- Internal methods -->
+
+      <constructor>
+        <![CDATA[
+          if (this.controller && this.controller.gridBoundCallback != undefined)
+            this.controller.gridBoundCallback();
+
+          // XXX This event was never actually implemented (bug 223411).
+          var event = document.createEvent("Events");
+          event.initEvent("contentgenerated", true, true);
+          this.dispatchEvent(event);
+        ]]>
+      </constructor>
+
+        <method name="_isIndexInBounds">
+        <parameter name="anIndex"/>
+        <body>
+          <![CDATA[
+            return anIndex >= 0 && anIndex < this.itemCount;
+          ]]>
+        </body>
+      </method>
+
+      <method name="_createItemElement">
+        <parameter name="aLabel"/>
+        <parameter name="aValue"/>
+        <body>
+          <![CDATA[
+            let item = this.ownerDocument.createElement("richgriditem");
+            item.control = this;
+            item.setAttribute("label", aLabel);
+            if (aValue)
+              item.setAttribute("value", aValue);
+
+            // copy over the richgrid's data-contextactions as each child is created
+            if (this.hasAttribute("data-contextactions")) {
+              item.setAttribute("data-contextactions", this.getAttribute("data-contextactions"));
+            }
+            return item;
+          ]]>
+        </body>
+      </method>
+
+      <method name="_fireOnSelect">
+        <body>
+          <![CDATA[
+            if (this.suppressOnSelect || this._suppressOnSelect)
+              return;
+
+            var event = document.createEvent("Events");
+            event.initEvent("select", true, true);
+            this.dispatchEvent(event);
+          ]]>
+        </body>
+      </method>
+      <method name="_fireOnSelectionChange">
+        <body>
+          <![CDATA[
+            // flush out selection-related cached properties so they get recalc'd next time
+            this._contextActions = null;
+
+            // fire an event?
+            if (this.suppressOnSelect || this._suppressOnSelect)
+              return;
+
+            var event = document.createEvent("Events");
+            event.initEvent("selectionchange", true, true);
+            this.dispatchEvent(event);
+          ]]>
+        </body>
+      </method>
+
+    </implementation>
+  </binding>
+
+  <binding id="richgrid-item">
+    <content>
+      <xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content">
+        <xul:hbox class="richgrid-icon-container">
+          <xul:box class="richgrid-icon-box"><xul:image xbl:inherits="src=iconURI"/></xul:box>
+          <xul:box flex="1" />
+        </xul:hbox>
+        <xul:description xbl:inherits="value=label" crop="end"/>
+      </xul:vbox>
+    </content>
+
+    <implementation>
+      <property name="_box" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-richgrid-item');"/>
+      <property name="color" onset="this._color = val; this.setColor();" onget="return this._color;"/>
+      <property name="selected"
+                onget="return this.getAttribute('selected') == 'true';"
+                onset="this.setAttribute('selected', val);"/>
+
+      <constructor>
+        <![CDATA[
+          // Bindings don't get bound until the item is displayed,
+          // so we have to reset the background color when we get
+          // created.
+          this.setColor();
+        ]]>
+      </constructor>
+
+      <property name="control">
+        <getter><![CDATA[
+          var parent = this.parentNode;
+          while (parent) {
+            if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
+              return parent;
+            parent = parent.parentNode;
+          }
+          return null;
+        ]]></getter>
+      </property>
+
+      <method name="setColor">
+        <body>
+          <![CDATA[
+            if (this.color != undefined) {
+              this._box.parentNode.setAttribute("customColorPresent", "true");
+              this._box.style.backgroundColor = this.color;
+            } else {
+              this._box.parentNode.removeAttribute("customColorPresent");
+            }
+          ]]>
+        </body>
+      </method>
+
+      <field name="_contextActions">null</field>
+      <property name="contextActions">
+        <getter>
+          <![CDATA[
+            if(!this._contextActions) {
+              this._contextActions = new Set();
+              let actionSet = this._contextActions;
+              let actions = this.getAttribute("data-contextactions");
+              if (actions) {
+                actions.split(/[,\s]+/).forEach(function(verb){
+                  actionSet.add(verb);
+                });
+              }
+            }
+            return this._contextActions;
+          ]]>
+        </getter>
+      </property>
+
+    </implementation>
+
+    <handlers>
+      <handler event="click" button="0">
+        <![CDATA[
+          // left-click/touch handler
+          this.control.handleItemClick(this, event);
+        ]]>
+      </handler>
+      <handler event="contextmenu">
+        <![CDATA[
+          // fires for right-click, long-click and (keyboard) contextmenu input
+          // TODO: handle cross-slide event when it becomes available,
+          // .. using contextmenu is a stop-gap measure to allow us to
+          // toggle the selected state of tiles in a grid
+          this.control.handleItemContextMenu(this, event);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/pageaction.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="pageaction" display="xul:hbox">
+    <content>
+      <xul:hbox align="center">
+        <xul:image xbl:inherits="src=image" class="pageaction-image"/>
+      </xul:hbox>
+      <xul:vbox pack="center" flex="1">
+        <xul:label class="pageaction-title" xbl:inherits="value=title" crop="end"/>
+        <xul:label class="pageaction-desc" value="" xbl:inherits="value=description" crop="end"/>
+      </xul:vbox>
+    </content>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/selectionoverlay.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+    xmlns:html="http://www.w3.org/1999/xhtml">
+  <binding id="selection-binding">  
+    <content>
+      <html:div flex="1" class="selection-overlay-inner window-width window-height" anonid="selection-overlay-inner">
+        <xul:stack>
+          <html:div anonid="selection-overlay-debug" class="window-width window-height"/>
+          <xul:toolbarbutton id="selectionhandle-start" label="^" left="0" top="10" hidden="false"/>
+          <xul:toolbarbutton id="selectionhandle-end" label="^" left="100" top="10" hidden="false"/>
+        </xul:stack>
+      </html:div>
+    </content>
+
+    <implementation implements="nsIDOMEventListener">
+      <constructor>
+        <![CDATA[
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+        ]]>
+      </destructor>
+
+      <field name="_selectionOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-inner").parentNode;</field>
+      <field name="_selectionDebugOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-debug");</field>
+
+      <property name="enabled">
+        <setter>
+          <![CDATA[
+            if (val) {
+              this._selectionOverlay.setAttribute("class", "selection-overlay window-width window-height");
+            } else {
+              this._selectionOverlay.setAttribute("class", "selection-overlay-hidden");
+              while(this._selectionDebugOverlay.hasChildNodes()) this._selectionDebugOverlay.removeChild(this._selectionDebugOverlay.firstChild);
+            }
+            return val;
+          ]]>
+        </setter>
+        <getter>
+          <![CDATA[
+          return (this._selectionOverlay.getAttribute("class") == "selection-overlay");
+        ]]>
+        </getter>
+      </property>
+
+      <property name="displayDebugLayer">
+        <setter>
+          <![CDATA[
+            if (val) {
+              this._selectionDebugOverlay.style.display = "block";
+              this._debugLayerVisible = true;
+            } else {
+              this._selectionDebugOverlay.style.display = "none";
+              this._debugLayerVisible = false;
+            }
+            return this._debugLayerVisible;
+          ]]>
+        </setter>
+        <getter>
+          <![CDATA[
+          if (this._debugLayerVisible == "undefined")
+            this._debugLayerVisible = false;
+          return this._debugLayerVisible;
+        ]]>
+        </getter>
+      </property>
+
+      <method name="init">
+        <body>
+          <![CDATA[
+          this.enabled = true;
+          ]]>
+        </body>
+      </method>
+
+      <method name="shutdown">
+        <body>
+          <![CDATA[
+          this.enabled = false;
+          ]]>
+        </body>
+      </method>
+
+      <method name="addDebugRect">
+        <parameter name="aLeft"/>
+        <parameter name="aTop"/>
+        <parameter name="aRight"/>
+        <parameter name="aBottom"/>
+        <parameter name="aColor"/>
+        <parameter name="aFill"/>
+        <parameter name="aId"/>
+        <body>
+          <![CDATA[
+            let e = document.createElement("div");
+            this._selectionDebugOverlay.appendChild(e);
+            e.style.position = "fixed";
+            e.style.left = aLeft +"px";
+            e.style.top = aTop +"px";
+            e.style.right = aRight +"px";
+            e.style.bottom = aBottom +"px";
+            e.style.maxWidth = (aRight - aLeft) +"px";
+            e.style.width = (aRight - aLeft) +"px";
+            e.style.maxHeight = (aBottom - aTop) +"px";
+            e.style.height = (aBottom - aTop) +"px";
+            if (aFill == undefined) {
+              e.style.backgroundColor = aColor;
+            } else {
+              if (aFill) {
+                e.style.backgroundColor = aColor;
+              } else {
+                e.style.border = "2px solid " + aColor;
+              }
+            }
+            e.style.opacity = "0.5";
+          ]]>
+        </body>
+      </method>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/tabs.xml
@@ -0,0 +1,173 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:html="http://www.w3.org/1999/xhtml"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="documenttab">
+    <content observes="bcast_urlbarState">
+      <xul:stack class="documenttab-container">
+        <xul:box anonid="thumbnail" class="documenttab-thumbnail" />
+        <xul:image anonid="favicon" class="documenttab-favicon"
+                   observes="bcast_urlbarState" width="26" height="26"/>
+
+        <xul:label anonid="title" class="documenttab-title" bottom="0" start="0" end="0" crop="end"/>
+        <xul:box anonid="selection" class="documenttab-crop"/>
+        <xul:box anonid="selection" class="documenttab-selection"/>
+        <xul:button anonid="close" class="documenttab-close" observes="bcast_urlbarState" end="0" top="0"
+                    onclick="event.stopPropagation(); document.getBindingParent(this)._onClose()"/>
+      </xul:stack>
+    </content>
+
+    <handlers>
+      <handler event="click" clickcount="1" action="this._onClick()"/>
+      <handler event="dblclick" action="this._onDoubleClick(); event.stopPropagation();"/>
+    </handlers>
+
+    <implementation>
+      <field name="_thumbnail" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "thumbnail");</field>
+      <field name="_close" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "close");</field>
+      <field name="_title" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "title");</field>
+      <field name="_favicon" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "favicon");</field>
+      <field name="_container" readonly="true">this.parentNode;</field>
+
+      <method name="_onClick">
+        <body>
+          <![CDATA[
+            this._container.selectedTab = this;
+            let selectFn = new Function("event", this._container.parentNode.getAttribute("onselect"));
+            selectFn.call(this);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_onDoubleClick">
+        <body>
+          <![CDATA[
+            this._container.selectedTab = this;
+            let selectFn = new Function("event", this._container.parentNode.getAttribute("ondbltap"));
+            selectFn.call(this);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_onClose">
+        <body>
+          <![CDATA[
+            let callbackFunc = this._container.parentNode.getAttribute("onclosetab");
+            let closeFn = new Function("event", callbackFunc);
+            closeFn.call(this);
+          ]]>
+        </body>
+      </method>
+
+      <method name="updateTitle">
+        <parameter name="title"/>
+        <body>
+          <![CDATA[
+            this._title.value = title;
+          ]]>
+        </body>
+      </method>
+
+      <method name="updateFavicon">
+        <parameter name="src"/>
+        <body>
+          <![CDATA[
+            this._favicon.src = src;
+          ]]>
+        </body>
+      </method>
+
+      <method name="updateThumbnailSource">
+        <parameter name="browser"/>
+        <body>
+          <![CDATA[
+            this._thumbnail.style.backgroundImage = "-moz-element(#" + browser.id + ")";
+          ]]>
+        </body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="tablist">
+    <content>
+      <xul:arrowscrollbox anonid="tabs-scrollbox" class="tabs-scrollbox" flex="1" orient="horizontal"
+        clicktoscroll="true" />
+    </content>
+
+    <handlers>
+      <handler event="dblclick" action="this._onDoubleClick();"/>
+    </handlers>
+
+    <implementation>
+      <field name="strip">document.getAnonymousElementByAttribute(this, "anonid", "tabs-scrollbox");</field>
+      <field name="_selectedTab">null</field>
+
+      <!-- Used by the chrome input handler -->
+      <property name="anonScrollBox"
+                readonly="true"
+                onget="return this.strip;"/>
+
+      <property name="selectedTab" onget="return this._selectedTab;">
+        <setter>
+          <![CDATA[
+            if (this._selectedTab)
+              this._selectedTab.removeAttribute("selected");
+
+            if (val)
+              val.setAttribute("selected", "true");
+
+            this._selectedTab = val;
+            this.strip.ensureElementIsVisible(val);
+          ]]>
+        </setter>
+      </property>
+
+      <method name="addTab">
+        <body>
+          <![CDATA[
+            let tab = document.createElement("documenttab");
+            this.strip.appendChild(tab);
+            return tab;
+          ]]>
+        </body>
+      </method>
+
+      <method name="removeTab">
+        <parameter name="aTab"/>
+        <body>
+          <![CDATA[
+            this.strip.removeChild(aTab);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_onDoubleTap">
+        <body>
+          <![CDATA[
+            new Function("event", this.getAttribute("ondoubletap")).call();
+          ]]>
+        </body>
+      </method>
+
+      <method name="_onDoubleClick">
+        <body>
+          <![CDATA[
+            // ignore mouse events if we're interacting with touch input
+            if (!InputSourceHelper.isPrecise)
+              return;
+            new Function("event", this.getAttribute("ondoubletap")).call();
+          ]]>
+        </body>
+      </method>
+
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bindings/toggleswitch.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+  <!ENTITY % checkboxDTD SYSTEM "chrome://browser/locale/checkbox.dtd">
+  %checkboxDTD;
+]>
+
+<bindings
+    xmlns="http://www.mozilla.org/xbl"
+    xmlns:xbl="http://www.mozilla.org/xbl"
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="checkbox-toggleswitch" display="xul:box" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
+    <content>
+      <xul:hbox>
+        <xul:description anonid="onlabel" class="onlabel" value="&checkbox.yes.label;" xbl:inherits="value=onlabel"/>
+        <xul:description anonid="offlabel" class="offlabel" value="&checkbox.no.label;" xbl:inherits="value=offlabel"/>
+        <xul:radiogroup anonid="group" xbl:inherits="disabled">
+          <xul:radio anonid="on" class="checkbox-radio-on"/>
+          <xul:radio anonid="off" class="checkbox-radio-off"/>
+        </xul:radiogroup>
+      </xul:hbox>
+    </content>
+    <implementation>
+      <constructor><![CDATA[
+        this.setChecked(this.checked);
+      ]]></constructor>
+
+      <field name="_group">
+        document.getAnonymousElementByAttribute(this, "anonid", "group");
+      </field>
+
+      <field name="_on">
+        document.getAnonymousElementByAttribute(this, "anonid", "on");
+      </field>
+
+      <field name="_onlabel">
+        document.getAnonymousElementByAttribute(this, "anonid", "onlabel");
+      </field>
+
+      <field name="_off">
+        document.getAnonymousElementByAttribute(this, "anonid", "off");
+      </field>
+
+      <field name="_offlabel">
+        document.getAnonymousElementByAttribute(this, "anonid", "offlabel");
+      </field>
+
+      <property name="onlabel"
+        onget="return this._onlabel.value"
+        onset="this._offlabel.value=val"/>
+
+      <property name="offlabel"
+        onget="return this._offlabel.value"
+        onset="this._offlabel.value=val"/>
+
+      <method name="setChecked">
+        <parameter name="aValue"/>
+        <body>
+        <![CDATA[
+          let change = (aValue != this.checked);
+
+          if (aValue) {
+            this.setAttribute("checked", "true");
+            this._group.selectedItem = this._on;
+          } else {
+            this.removeAttribute("checked");
+            this._group.selectedItem = this._off;
+          }
+
+          if (change) {
+            this.fireEvent("CheckboxStateChange");
+            this.fireEvent("command");
+          }
+
+          return aValue;
+        ]]>
+        </body>
+      </method>
+
+      <method name="fireEvent">
+        <parameter name="aName"/>
+        <body>
+          <![CDATA[
+            var event = document.createEvent("Events");
+            event.initEvent(aName, true, true);
+            this.dispatchEvent(event);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_onClickOrTap">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            aEvent.stopPropagation();
+            this.setChecked(!this.checked);
+          ]]>
+        </body>
+      </method>
+    </implementation>
+    <handlers>
+      <handler event="click" button="0" phase="capturing">
+        <![CDATA[
+          this._onClickOrTap(event);
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="setting-fulltoggle-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
+    <implementation>
+      <constructor>
+        <![CDATA[
+          this.input.classList.add("toggleswitch");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+
+  <binding id="setting-fulltoggle-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-boolint">
+    <implementation>
+      <constructor>
+        <![CDATA[
+          this.input.classList.add("toggleswitch");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+
+  <binding id="setting-fulltoggle-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-localized-bool">
+    <implementation>
+      <constructor>
+        <![CDATA[
+          this.input.classList.add("toggleswitch");
+        ]]>
+      </constructor>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/bookmarks.js
@@ -0,0 +1,364 @@
+/* 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/. */
+
+/**
+ * Utility singleton for manipulating bookmarks.
+ */
+var Bookmarks = {
+  get metroRoot() {
+    return PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0];
+  },
+
+  logging: false,
+  log: function(msg) {
+    if (this.logging) {
+      Services.console.logStringMessage(msg);
+    }
+  },
+
+  /*
+   * fixupColorFormat - convert a decimal color value to a valid
+   * css color string of the format '#123456'
+   */ 
+  fixupColorFormat: function bv_fixupColorFormat(aColor) {
+    let color = aColor.toString(16);
+    while (color.length < 6)
+      color = "0" + color;
+    return "#" + color;
+  },
+
+  getFaveIconPrimaryColor: function bh_getFaveIconPrimaryColor(aBookmarkId) {
+    if (PlacesUtils.annotations.itemHasAnnotation(aBookmarkId, 'metro/faveIconColor'))
+      return PlacesUtils.annotations.getItemAnnota