PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/tv/osx/plat/frontends/widgets/browser.py

https://github.com/geoffl/miro
Python | 281 lines | 178 code | 49 blank | 54 comment | 14 complexity | a12ce248a760a2236224e7c3e5fa9764 MD5 | raw file
  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
  3. # Participatory Culture Foundation
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. #
  19. # In addition, as a special exception, the copyright holders give
  20. # permission to link the code of portions of this program with the OpenSSL
  21. # library.
  22. #
  23. # You must obey the GNU General Public License in all respects for all of
  24. # the code used other than OpenSSL. If you modify file(s) with this
  25. # exception, you may extend this exception to your version of the file(s),
  26. # but you are not obligated to do so. If you do not wish to do so, delete
  27. # this exception statement from your version. If you delete this exception
  28. # statement from all source files in the program, then also delete it here.
  29. """miro.plat.frontends.widgets.browser -- Web Browser widget. """
  30. import os
  31. import logging
  32. import tempfile
  33. from AppKit import *
  34. from Foundation import *
  35. from WebKit import *
  36. from objc import YES, NO, nil
  37. from PyObjCTools import AppHelper
  38. from miro import app
  39. from miro import prefs
  40. from miro.plat.frontends.widgets.base import Widget
  41. class Browser(Widget):
  42. """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class."""
  43. def __init__(self):
  44. Widget.__init__(self)
  45. self.url = None
  46. self.create_signal('net-start')
  47. self.create_signal('net-stop')
  48. self.create_signal('download-finished')
  49. self.delegate = BrowserDelegate.alloc().initWithBrowser_(self)
  50. self.view = MiroWebView.alloc().initWithFrame_(NSRect((0,0), self.calc_size_request()))
  51. self.view.setMaintainsBackForwardList_(YES)
  52. # XXX SL and better only
  53. if self.view.respondsToSelector_('setShouldUpdateWhileOffscreen:'):
  54. self.view.setShouldUpdateWhileOffscreen_(NO)
  55. self.view.setApplicationNameForUserAgent_("%s %s/%s (%s)" % \
  56. ('Safari/531.2+', # FIXME hack for #17370
  57. app.config.get(prefs.SHORT_APP_NAME),
  58. app.config.get(prefs.APP_VERSION),
  59. app.config.get(prefs.PROJECT_URL),))
  60. def _set_webkit_delegates(self, delegate):
  61. self.view.setPolicyDelegate_(delegate)
  62. self.view.setResourceLoadDelegate_(delegate)
  63. self.view.setFrameLoadDelegate_(delegate)
  64. self.view.setUIDelegate_(delegate)
  65. self.view.setDownloadDelegate_(delegate)
  66. def viewport_created(self):
  67. Widget.viewport_created(self)
  68. if self.view.isLoading():
  69. # We haven't set up our delegates yet, but have already started
  70. # loading a webpage. Send the net-start signal now because the
  71. # delegate will miss it.
  72. self.emit("net-start")
  73. self._set_webkit_delegates(self.delegate)
  74. def remove_viewport(self):
  75. self._hack_remove_viewport()
  76. self._set_webkit_delegates(nil)
  77. def _hack_remove_viewport(self):
  78. # This was stolen from Widget.remove_viewport() and modified just move
  79. # the viewport to a hidden spot.
  80. # This works, but it's pretty ugly. Let's fix for 4.0
  81. if self.viewport is not None:
  82. offscreen_rect = NSRect((-5000, -5000), self.view.frame().size)
  83. self.viewport.reposition(offscreen_rect)
  84. # When we re-show the view, miro will just position it back to the
  85. # correct place. Therefore, don't remove from wrappermap because
  86. # miro won't call wrappermap.add() back
  87. #if self.CREATES_VIEW:
  88. #wrappermap.remove(self.view)
  89. def viewport_repositioned(self):
  90. # gets called when we need to re-show our view after
  91. # _hack_remove_viewport()
  92. self._set_webkit_delegates(self.delegate)
  93. self.queue_redraw()
  94. def calc_size_request(self):
  95. return (200, 100) # Seems like a reasonable minimum size
  96. def navigate(self, url):
  97. self.url = url
  98. request = NSURLRequest.requestWithURL_(NSURL.URLWithString_(url))
  99. self.view.mainFrame().loadRequest_(request)
  100. def get_current_url(self):
  101. return self.view.mainFrameURL()
  102. def get_current_title(self):
  103. return self.view.mainFrameTitle()
  104. def forward(self):
  105. self.view.goForward()
  106. def back(self):
  107. self.view.goBack()
  108. def reload(self):
  109. self.view.reload_(nil)
  110. def stop(self):
  111. self.view.stopLoading_(nil)
  112. def can_go_back(self):
  113. return self.view.canGoBack()
  114. def can_go_forward(self):
  115. return self.view.canGoForward()
  116. def destroy(self):
  117. pass
  118. ###############################################################################
  119. class MiroWebView (WebView):
  120. def performDragOperation_(self, sender):
  121. return NO
  122. ###############################################################################
  123. class BrowserDelegate (NSObject):
  124. def initWithBrowser_(self, browser):
  125. self = super(BrowserDelegate, self).init()
  126. self.browser = browser
  127. self.openPanelContextID = 1
  128. self.openPanelContext = dict()
  129. self._downloads = {}
  130. return self
  131. def webView_didStartProvisionalLoadForFrame_(self, webview, frame):
  132. self.browser.emit('net-start')
  133. def webView_didFinishLoadForFrame_(self, webview, frame):
  134. self.browser.emit('net-stop')
  135. def webView_decidePolicyForNavigationAction_request_frame_decisionListener_(
  136. self, webview, action_information, request, frame, listener):
  137. url = unicode(request.URL())
  138. if self.browser.should_load_url(url):
  139. listener.use()
  140. else:
  141. listener.ignore()
  142. def webView_decidePolicyForMIMEType_request_frame_decisionListener_(self, webview, mtype, request, frame, listener):
  143. url = unicode(request.URL())
  144. if self.browser.should_download_url(url, mtype):
  145. listener.download()
  146. elif self.browser.should_load_mimetype(url, mtype):
  147. listener.use()
  148. else:
  149. listener.ignore()
  150. # Intercept external links requests
  151. def webView_decidePolicyForNewWindowAction_request_newFrameName_decisionListener_(self, webView, info, request, name, listener):
  152. url = info["WebActionOriginalURLKey"]
  153. NSWorkspace.sharedWorkspace().openURL_(url)
  154. listener.ignore()
  155. def webView_didFailProvisionalLoadWithError_forFrame_(self, webview, error, frame):
  156. urlError = (error.domain() == NSURLErrorDomain)
  157. certError = (error.code() in (NSURLErrorServerCertificateHasBadDate,
  158. NSURLErrorServerCertificateHasUnknownRoot,
  159. NSURLErrorServerCertificateUntrusted))
  160. if urlError and certError:
  161. request = frame.provisionalDataSource().request()
  162. if request is nil:
  163. request = frame.dataSource().request()
  164. url = request.URL()
  165. allowed = [app.config.get(prefs.CHANNEL_GUIDE_URL),
  166. app.config.get(prefs.CHANNEL_GUIDE_FIRST_TIME_URL)]
  167. if url.absoluteString() in allowed:
  168. # The [NSURLRequest setAllowsAnyHTTPSCertificate:forHost:] selector is
  169. # not documented anywhere, so I assume it is not public. It is however
  170. # a very clean and easy way to allow us to load our channel guide from
  171. # https, so let's use it here anyway :)
  172. NSURLRequest.setAllowsAnyHTTPSCertificate_forHost_(YES, url.host())
  173. # Now reload
  174. frame.loadRequest_(request)
  175. def webView_createWebViewWithRequest_(self, webView, request):
  176. global jsOpened
  177. webView = WebView.alloc().init()
  178. webView.setFrameLoadDelegate_(jsOpened)
  179. webView.mainFrame().loadRequest_(request)
  180. return webView
  181. def webView_resource_willSendRequest_redirectResponse_fromDataSource_(self, webview, resourceCookie, request, redirectResponse, dataSource):
  182. url = request.URL().absoluteString()
  183. if isinstance(request, NSMutableURLRequest):
  184. language = os.environ['LANGUAGE'].split(':')[0].replace('_', '-')
  185. request.setValue_forHTTPHeaderField_(language, u'Accept-Language')
  186. request.setValue_forHTTPHeaderField_(u'1', u'X-Miro')
  187. return request
  188. def webView_runOpenPanelForFileButtonWithResultListener_(self, webview, listener):
  189. self.openPanelContextID += 1
  190. self.openPanelContext[self.openPanelContextID] = listener
  191. panel = NSOpenPanel.openPanel()
  192. panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
  193. NSHomeDirectory(),
  194. nil,
  195. nil,
  196. webview.window(),
  197. self,
  198. 'openPanelDidEnd:returnCode:contextInfo:',
  199. self.openPanelContextID)
  200. @AppHelper.endSheetMethod
  201. def openPanelDidEnd_returnCode_contextInfo_(self, panel, result, contextID):
  202. listener = self.openPanelContext[contextID]
  203. del self.openPanelContext[contextID]
  204. if result == NSOKButton:
  205. filenames = panel.filenames()
  206. listener.chooseFilename_(filenames[0])
  207. def webView_addMessageToConsole_(self, webview, message):
  208. logging.jsalert(message)
  209. def webView_runJavaScriptAlertPanelWithMessage_(self, webview, message):
  210. logging.jsalert(message)
  211. # NSURLDownloadDelegate methods
  212. def download_decideDestinationWithSuggestedFilename_(self, download, filename):
  213. prefix, suffix = os.path.splitext(filename)
  214. fd, path = tempfile.mkstemp(suffix=suffix, prefix=prefix)
  215. os.close(fd)
  216. download.setDestination_allowOverwrite_(path, NO)
  217. def downloadDidFinish_(self, download):
  218. path = self._downloads.pop(download)
  219. self.browser.emit('net-stop')
  220. self.browser.emit('download-finished', 'file://%s' % path)
  221. def download_didCreateDestination_(self, download, path):
  222. self._downloads[download] = path
  223. def downloadShouldUseCredentialStorage_(self, download):
  224. return YES
  225. def download_shouldDecodeSourceDataOfMIMEType_(self, download, mime_type):
  226. return YES
  227. ###############################################################################
  228. class JSOpened (NSObject):
  229. def webView_willPerformClientRedirectToURL_delay_fireDate_forFrame_(self, webView, url, delay, fireDate, frame):
  230. webView.stopLoading_(nil)
  231. NSWorkspace.sharedWorkspace().openURL_(url)
  232. jsOpened = JSOpened.alloc().init()