Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / uber / uber.js
index 5793ce3..a2cbfbd 100644 (file)
@@ -21,6 +21,15 @@ cr.define('uber', function() {
   var navFrame;
 
   /**
+   * A queue of method invocations on one of the iframes; if the iframe has not
+   * loaded by the time there is a method to invoke, delay the invocation until
+   * it is ready.
+   * @type {Object}
+   * @private
+   */
+  var queuedInvokes = {};
+
+  /**
    * Handles page initialization.
    */
   function onLoad(e) {
@@ -92,8 +101,22 @@ cr.define('uber', function() {
    * @param {Event} e The history event.
    */
   function onPopHistoryState(e) {
-    if (e.state && e.state.pageId)
-      showPage(e.state.pageId, HISTORY_STATE_OPTION.NONE);
+    // Use the URL to determine which page to route to.
+    var params = resolvePageInfo();
+
+    // If the page isn't the current page, load it fresh. Even if the page is
+    // already loaded, it may have state not reflected in the URL, such as the
+    // history page's "Remove selected items" overlay. http://crbug.com/377386
+    if (getRequiredElement(params.id) !== getSelectedIframe())
+      showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path);
+
+    // Either way, send the state down to it.
+    //
+    // Note: This assumes that the state and path parameters for every page
+    // under this origin are compatible. All of the downstream pages which
+    // navigate use pushState and replaceState.
+    invokeMethodOnPage(params.id, 'popState',
+                       {state: e.state, path: '/' + params.path});
   }
 
   /**
@@ -126,12 +149,16 @@ cr.define('uber', function() {
    * @param {Event} e The posted object.
    */
   function handleWindowMessage(e) {
+    e = /** @type{!MessageEvent.<!{method: string, params: *}>} */(e);
     if (e.data.method === 'beginInterceptingEvents') {
       backgroundNavigation();
     } else if (e.data.method === 'stopInterceptingEvents') {
       foregroundNavigation();
-    } else if (e.data.method === 'setPath') {
-      setPath(e.origin, e.data.params.path);
+    } else if (e.data.method === 'ready') {
+      pageReady(e.origin);
+    } else if (e.data.method === 'updateHistory') {
+      updateHistory(e.origin, e.data.params.state, e.data.params.path,
+                    e.data.params.replace);
     } else if (e.data.method === 'setTitle') {
       setTitle(e.origin, e.data.params.title);
     } else if (e.data.method === 'showPage') {
@@ -141,9 +168,9 @@ cr.define('uber', function() {
     } else if (e.data.method === 'navigationControlsLoaded') {
       onNavigationControlsLoaded();
     } else if (e.data.method === 'adjustToScroll') {
-      adjustToScroll(e.data.params);
+      adjustToScroll(/** @type {number} */(e.data.params));
     } else if (e.data.method === 'mouseWheel') {
-      forwardMouseWheel(e.data.params);
+      forwardMouseWheel(/** @type {Object} */(e.data.params));
     } else {
       console.error('Received unexpected message', e.data);
     }
@@ -177,28 +204,30 @@ cr.define('uber', function() {
 
     if (isRTL()) {
       uber.invokeMethodOnWindow(navFrame.firstChild.contentWindow,
-                                'setContentChanging',
-                                enabled);
+                                'setContentChanging', enabled);
     }
   }
 
   /**
    * Get an iframe based on the origin of a received post message.
    * @param {string} origin The origin of a post message.
-   * @return {!HTMLElement} The frame associated to |origin| or null.
+   * @return {!Element} The frame associated to |origin| or null.
    */
   function getIframeFromOrigin(origin) {
     assert(origin.substr(-1) != '/', 'invalid origin given');
     var query = '.iframe-container > iframe[src^="' + origin + '/"]';
-    return document.querySelector(query);
+    var element = document.querySelector(query);
+    assert(element);
+    return /** @type {!Element} */(element);
   }
 
   /**
    * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)).
+   * @param {Object} state The page's state object for the navigation.
    * @param {string} path The new /path/ to be set after the page name.
    * @param {number} historyOption The type of history modification to make.
    */
-  function changePathTo(path, historyOption) {
+  function changePathTo(state, path, historyOption) {
     assert(!path || path.substr(-1) != '/', 'invalid path given');
 
     var histFunc;
@@ -210,25 +239,31 @@ cr.define('uber', function() {
     assert(histFunc, 'invalid historyOption given ' + historyOption);
 
     var pageId = getSelectedIframe().id;
-    var args = [{pageId: pageId}, '', '/' + pageId + '/' + (path || '')];
+    var args = [state, '', '/' + pageId + '/' + (path || '')];
     histFunc.apply(window.history, args);
   }
 
   /**
-   * Sets the "path" of the page (actually the path after the first '/' char).
-   * @param {Object} origin The origin of the source iframe.
-   * @param {string} title The new "path".
+   * Adds or replaces the current history entry based on a navigation from the
+   * source iframe.
+   * @param {string} origin The origin of the source iframe.
+   * @param {Object} state The source iframe's state object.
+   * @param {string} path The new "path" (e.g. "/createProfile").
+   * @param {boolean} replace Whether to replace the current history entry.
    */
-  function setPath(origin, path) {
+  function updateHistory(origin, state, path, replace) {
     assert(!path || path[0] != '/', 'invalid path sent from ' + origin);
+    var historyOption =
+        replace ? HISTORY_STATE_OPTION.REPLACE : HISTORY_STATE_OPTION.PUSH;
     // Only update the currently displayed path if this is the visible frame.
-    if (getIframeFromOrigin(origin).parentNode == getSelectedIframe())
-      changePathTo(path, HISTORY_STATE_OPTION.REPLACE);
+    var container = getIframeFromOrigin(origin).parentNode;
+    if (container == getSelectedIframe())
+      changePathTo(state, path, historyOption);
   }
 
   /**
    * Sets the title of the page.
-   * @param {Object} origin The origin of the source iframe.
+   * @param {string} origin The origin of the source iframe.
    * @param {string} title The title of the page.
    */
   function setTitle(origin, title) {
@@ -244,15 +279,48 @@ cr.define('uber', function() {
   }
 
   /**
-   * Selects a subpage. This is called from uber-frame.
+   * Invokes a method on a subpage. If the subpage has not signaled readiness,
+   * queue the message for when it does.
    * @param {string} pageId Should match an id of one of the iframe containers.
-   * @param {integer} historyOption Indicates whether we should push or replace
+   * @param {string} method The name of the method to invoke.
+   * @param {Object=} opt_params Optional property page of parameters to pass to
+   *     the invoked method.
+   */
+  function invokeMethodOnPage(pageId, method, opt_params) {
+    var frame = $(pageId).querySelector('iframe');
+    if (!frame || !frame.dataset.ready) {
+      queuedInvokes[pageId] = (queuedInvokes[pageId] || []);
+      queuedInvokes[pageId].push([method, opt_params]);
+    } else {
+      uber.invokeMethodOnWindow(frame.contentWindow, method, opt_params);
+    }
+  }
+
+  /**
+   * Called in response to a page declaring readiness. Calls any deferred method
+   * invocations from invokeMethodOnPage.
+   * @param {string} origin The origin of the source iframe.
+   */
+  function pageReady(origin) {
+    var frame = getIframeFromOrigin(origin);
+    var container = frame.parentNode;
+    frame.dataset.ready = true;
+    var queue = queuedInvokes[container.id] || [];
+    queuedInvokes[container.id] = undefined;
+    for (var i = 0; i < queue.length; i++) {
+      uber.invokeMethodOnWindow(frame.contentWindow, queue[i][0], queue[i][1]);
+    }
+  }
+
+  /**
+   * Selects and navigates a subpage. This is called from uber-frame.
+   * @param {string} pageId Should match an id of one of the iframe containers.
+   * @param {number} historyOption Indicates whether we should push or replace
    *     browser history.
    * @param {string} path A sub-page path.
    */
   function showPage(pageId, historyOption, path) {
     var container = $(pageId);
-    var lastSelected = document.querySelector('.iframe-container.selected');
 
     // Lazy load of iframe contents.
     var sourceUrl = container.dataset.url + (path || '');
@@ -267,9 +335,11 @@ cr.define('uber', function() {
       // content frame is as we don't have access to its contentWindow's
       // location, so just replace every time until necessary to do otherwise.
       frame.contentWindow.location.replace(sourceUrl);
+      frame.dataset.ready = false;
     }
 
     // If the last selected container is already showing, ignore the rest.
+    var lastSelected = document.querySelector('.iframe-container.selected');
     if (lastSelected === container)
       return;
 
@@ -299,12 +369,15 @@ cr.define('uber', function() {
     uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected');
 
     if (historyOption != HISTORY_STATE_OPTION.NONE)
-      changePathTo(path, historyOption);
+      changePathTo({}, path, historyOption);
 
     if (container.dataset.title)
       document.title = container.dataset.title;
-    $('favicon').href = 'chrome://theme/' + container.dataset.favicon;
-    $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x';
+    assert('favicon' in container.dataset);
+
+    var dataset = /** @type {{favicon: string}} */(container.dataset);
+    $('favicon').href = 'chrome://theme/' + dataset.favicon;
+    $('favicon2x').href = 'chrome://theme/' + dataset.favicon + '@2x';
 
     updateNavigationControls();
   }