//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.managers { import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.external.ExternalInterface; import flash.net.navigateToURL; import flash.net.URLRequest; import mx.core.FlexGlobals; import mx.events.BrowserChangeEvent; /** * Dispatched when the fragment property is changed either * by the user interacting with the browser, invoking an * application in Apollo * or by code setting the property. * * @eventType mx.events.BrowserChangeEvent.URL_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="urlChange", type="flash.events.Event")] /** * Dispatched when the fragment property is changed * by the browser. * * @eventType mx.events.BrowserChangeEvent.BROWSER_URL_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="browserURLChange", type="mx.events.BrowserChangeEvent")] /** * Dispatched when the fragment property is changed * by the application via setFragment * * @eventType mx.events.BrowserChangeEvent.APPLICATION_URL_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="applicationURLChange", type="mx.events.BrowserChangeEvent")] [ExcludeClass] /** * @private * The BrowserManager is a Singleton manager that acts as * a proxy between the browser and the application. * It provides access to the URL in the browser address * bar similar to accessing document.location in Javascript. * Events are dispatched as the url property is changed. * Listeners can then respond, alter the url, and/or block others * from getting the event. * * For desktop applications, the BrowserManager * provides access to the command-line parameters used to * invoke the application. The url property will be the concatenated * string representing all of the command-line parameters separated * by semi-colons. * */ public class BrowserManagerImpl extends EventDispatcher implements IBrowserManager { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private */ private static var instance:IBrowserManager; private var _defaultFragment:String = ""; private var _browserUserAgent:String; private var _browserPlatform:String; private var _isFirefoxMac:Boolean; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * @private */ public static function getInstance():IBrowserManager { if (!instance) instance = new BrowserManagerImpl(); return instance; } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function BrowserManagerImpl() { super(); // we want to reduce dependencies for non-flex apps that use resources (e.g. rpc) var systemManager:Object = SystemManagerGlobals.topLevelSystemManagers; if (systemManager) systemManager = systemManager[0]; if (systemManager) { // figure out if we're top level, even if bootstrapped var sandboxRoot:Object = systemManager.getSandboxRoot(); if (!sandboxRoot.dispatchEvent(new Event("mx.managers::BrowserManager", false, true))) { // if someone answered, then we're not the first BM browserMode = false; return; } try { // see if we can walk to the stage var parent:Object = sandboxRoot.parent; while (parent) { if (sandboxRoot.parent is Stage) { break; } else { parent = parent.parent; } } } catch (e:Error) { browserMode = false; return; } sandboxRoot.addEventListener("mx.managers::BrowserManager", sandboxBrowserManagerHandler, false, 0, true); } try { ExternalInterface.addCallback("browserURLChange", browserURLChangeBrowser); ExternalInterface.addCallback("debugTrace", debugTrace); } catch(e:Error) { // not supported in all environments browserMode = false; } } private var browserMode:Boolean = true; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // base //---------------------------------- private var _base:String; [Bindable("urlChange")] /** * The portion of current URL before the '#' as it appears * in the browser address bar. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get base():String { return _base; } //---------------------------------- // fragment //---------------------------------- private var _fragment:String; [Bindable("urlChange")] /** * The portion of current URL after the '#' as it appears * in the browser address bar, or the default fragment * used in setup() if there is nothing after the '#'. * Use setFragment to change this value. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get fragment():String { if (_fragment && _fragment.length) return _fragment; return _defaultFragment; } //---------------------------------- // title //---------------------------------- private var _title:String; [Bindable("urlChange")] /** * The title of the app as it should appear in the * browser history * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get title():String { return _title; } //---------------------------------- // url //---------------------------------- private var _url:String; [Bindable("urlChange")] /** * The current URL as it appears in the browser address bar. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get url():String { return _url; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Initialize the BrowserManager. The BrowserManager will get the initial URL. If it * has a fragment, it will dispatch BROWSER_URL_CHANGE, so add your event listener * before calling this method. * * @param defaultFragment the fragment to use if no fragment in the initial URL. * @param defaultTitle the title to use if no fragment in the initial URL. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function init(defaultFragment:String = "", defaultTitle:String = ""):void { if ("historyManagementEnabled" in FlexGlobals.topLevelApplication) FlexGlobals.topLevelApplication.historyManagementEnabled = false; setup(defaultFragment, defaultTitle); } /** * Initialize the BrowserManager. The HistoryManager calls this method to * prepare the BrowserManager for further calls from the HistoryManager. Use * of HistoryManager and setFragment calls from the application is * not supported, so the init() method sets * FlexGlobals.topLevelApplication.historyManagementEnabled to false to disable * the HistoryManager * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function initForHistoryManager():void { setup("", ""); } private function setup(defaultFragment:String, defaultTitle:String):void { if (!browserMode) return; _defaultFragment = defaultFragment; _url = ExternalInterface.call("BrowserHistory.getURL"); // probably no support in html wrapper if (!_url) { browserMode = false; return; } _browserUserAgent = ExternalInterface.call("BrowserHistory.getUserAgent"); _browserPlatform = ExternalInterface.call("BrowserHistory.getPlatform"); // Unlike browser.js we specifically test for the Firefox browser (vs. other // Gecko or Mozilla derivatives), as the bug fix included in this file that // leverages this flag is very specific to Firefox/Mac. _isFirefoxMac = (_browserUserAgent && _browserPlatform && _browserUserAgent.indexOf("Firefox") > -1 && _browserPlatform.indexOf("Mac") > -1); var pos:int = _url.indexOf('#'); if (pos == -1 || pos == _url.length - 1) { _base = _url; _fragment = ''; _title = defaultTitle; ExternalInterface.call("BrowserHistory.setDefaultURL", defaultFragment); setTitle(defaultTitle); } else { _base = _url.substring(0, pos); _fragment = _url.substring(pos + 1); _title = ExternalInterface.call("BrowserHistory.getTitle"); ExternalInterface.call("BrowserHistory.setDefaultURL", _fragment); //have to force a refresh of the application. if (_fragment != _defaultFragment) browserURLChange(_fragment, true); } } /** * Change the fragment of the url after the '#' in the browser. * An attempt will be made to track this URL in the browser's * history. * * If the title is set, the old title in the browser is replaced * by the new title. * * To actually store the URL, a JavaScript * method named setBrowserURL() will be called. * The application's HTML wrapper must have that method which * must implement a mechanism for taking this * value and registering it with the browser's history scheme * and address bar. * * When set, the APPLICATION_URL_CHANGE event is sent. If the event * is cancelled the setBrowserURL() will not be called. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setFragment(value:String):void { if (!browserMode) return; //value = (value == "") ? _defaultFragment : value; var lastURL:String = _url; var lastFragment:String = _fragment; _url = base + '#' + value; _fragment = value; if (dispatchEvent(new BrowserChangeEvent(BrowserChangeEvent.APPLICATION_URL_CHANGE, false, true, _url, lastURL))) { if (!_isFirefoxMac) { ExternalInterface.call("BrowserHistory.setBrowserURL", value, ExternalInterface.objectID); } else { // We need to avoid updating our browser URL with ExternalInterface, when we are // running within Firefox/Mac. Player rendering bug logged 2276859. var urlReq:URLRequest = new URLRequest("javascript:BrowserHistory.setBrowserURL('" + value + "','" + ExternalInterface.objectID + "');"); navigateToURL( urlReq , "_self" ); } dispatchEvent(new BrowserChangeEvent(BrowserChangeEvent.URL_CHANGE, false, false, _url, lastURL)); } else { _fragment = lastFragment; _url = lastURL; } } /** * Change the title in the browser. * Does not affect the browser's * history. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setTitle(value:String):void { if (!browserMode) return; ExternalInterface.call("BrowserHistory.setTitle", value); _title = ExternalInterface.call("BrowserHistory.getTitle"); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- /** * @private * Callback from browser when the URL has been changed * in the browser. */ private function browserURLChangeBrowser(fragment:String):void { browserURLChange(fragment, false); } private function browserURLChange(fragment:String, force:Boolean = false):void { //trace("browserURLChange: |" + decodeURI(fragment) + "|, |" + decodeURI(_fragment) + "|" + ", " + force.toString()); if (((fragment != null) && (decodeURI(_fragment) != decodeURI(fragment))) || force) { _fragment = fragment; var lastURL:String = url; _url = _base + '#' + fragment; dispatchEvent(new BrowserChangeEvent(BrowserChangeEvent.BROWSER_URL_CHANGE, false, false, url, lastURL)); dispatchEvent(new BrowserChangeEvent(BrowserChangeEvent.URL_CHANGE, false, false, url, lastURL)); } } private function sandboxBrowserManagerHandler(event:Event):void { // cancel event to indicate that the message was heard event.preventDefault(); } //-------------------------------------------------------------------------- // // Diagnostics // //-------------------------------------------------------------------------- private function debugTrace(s:String):void { trace(s); } } }