Add webjump integration with OpenSearch search engines
[conkeror.git] / content / context.js
blobb072a34be688381691b1fd86e063e5f040d6f0f1
1 /*
2 The question is how to implement a context menu in Conkeror as elegantly as
3 possible, such that all its functionality is exposed not only to a gui mouse
4 interface, but also to keyboard interfaces with or without gui, the gui being
5 modularized so it can take many different forms beyond the classic style popup
6 menu.
8 Ignoring the UI for a moment, let's look at what a context menu's job really
9 is, and determine what exactly we mean by this vague noun, `context'.
11 Our module receives from its caller a DOM node.  The caller wants to know what
12 methods can be called on that node.  That sounds a lot like virtual-table
13 lookup in an object orientation system.
15 We received a node object from the caller, but our task concerns the context
16 of the node, which means traversing *up* the DOM, not down.  We can think of
17 our data type, then, as a conceptual--rather than a literal--type: the union
18 of a DOM node plus the environment in which it is embedded.
20 The environment comprises things of interest--things whose presence or absence
21 determines whether some method(s) will be valid for the context.  Is the node
22 an Image, for which we should deliver methods such as save_image and
23 follow_image?  Is the node inside a hyperlink for which we should deliver
24 methods like copy_link_location and follow_link_in_buffer?  Is the node
25 embedded in a navigable browser in which we can go back, or forward, or
26 reload, or stop?  These are examples of supertypes, again conceptual types,
27 not literal types as far as Javascript is concerned.  I used the word
28 `deliver', in the sense that we will put those methods into the virtual table
29 we build for the caller.  From the other side, though, the point of view of
30 the caller, the context-object's type appears to `inherit' those methods from
31 supertypes.
33 But these supertypes do not form a simple linear hierarchy.  Our Image
34 supertype is not a subclass of our Hyperlink supertype, or vice versa.  They
35 are separate concepts that coexist in our context.  Therefore, we can further
36 categorize our conceptual object system as a multiple inheritance object
37 system.
39 If our types are conceptual, not literal, how do we know whether or not a
40 given type is a supertype of our context object's type?  We cannot use
41 Javascript's `instanceof' operator.  Instead, we use predicate functions.  A
42 supertype can therefore be implemented as a predicate associated with methods.
43 When the predicate evaluates true on an object, that object is a subclass of
44 the supertype it represents, and we can add the methods provided by the
45 supertype to our virtual table.  In other words, this is how we implement
46 inheritance.
48 This model of a conceptual object system deeply informs the structure of our
49 code.  We are no longer dealing with a hairy procedure of side-effect code
50 that must all be done in just the right way to put the desired items in a
51 context menu.  We have a structured system that can be broken down into
52 sub-components, each of which can be understood separately, and upon which we
53 are free to build whatever kind of UI we desire.  The context system takes a
54 DOM node, and delivers methods appropriate to its context.  The UI's only task
55 then, is how to present those methods to the user.
57 --RetroJ
61 function has_properties_p (obj) {
62     for (var i in obj)
63         return true;
64     return false;
68 // call like: context_methods (document.popupNode);
69 function context_methods (node) {
70     var methods = {};
72     /*
73      * `inherit' will add the named method to the named interface.
74      */ 
75     function inherit (method_interface, name, method) {
76         if (! (method_interface in methods)) {
77             methods[method_interface] = {};
78         }
79         if (! (name in methods[method_interface])) {
80             methods[method_interface][name] = method;
81         }
82     }
84     /*
85      * `inherit_interface' will set the named interface to the group of
86      * methods provided, if that interface is not yet set.  This allows us to
87      * have method groups and `all-or-none' inheritance for the whole group.
88      */ 
89     function inherit_interface (name, interface_methods) {
90         if (! (name in methods)) {
91             methods[name] = interface_methods;
92         }
93     }
95     // Zeroeth, see if we are a subclass of simple stuff.
96     //
97     if (getWebNavigation().canGoBack)
98         inherit ('nav', 'back', function () { call_interactively('go-back',[1]); });
99     if (getWebNavigation().canGoForward)
100         inherit ('nav', 'forward', function () { call_interactively('go-forward',[1]); });
101     inherit ('nav', 'reload', function () { reload(); });
102     inherit ('nav', 'stop', function () { stop_loading(); });
104     // page interface
105     inherit ('page', 'save_page', function () { call_interactively('save-page'); });
107     // See if the user clicked in a frame.
108     if (node.ownerDocument != window.content.document) { // XXX: poor test for being a frame.
109         // add methods for inFrame Supertype
110         inherit ('frame', 'show_only_this_frameset_frame', function () { open_url_in (1, node.ownerDocument.location.href); });
111         inherit ('frame', 'open_frameset_frame_in_buffer', function () { open_url_in (4, node.ownerDocument.location.href); });
112         inherit ('frame', 'open_frameset_frame_in_frame', function () { open_url_in (16, node.ownerDocument.location.href); });
113         inherit ('frame', 'reload_frameset_frame', function () { node.ownerDocument.location.reload (); });
114         inherit ('frame', 'copy_frameset_frame_location', function () { call_interactively('copy_frameset_frame_location'); });
115         inherit ('frame', 'save_frameset_frame_page', function () { call_interactively('save-page',[node.ownerDocument]); });
116         inherit ('frame', 'view_frameset_frame_source', function () { call_interactively('view-source',[node.ownerDocument.location.href]); });
117     }
120     // First, case-analysis for nodes that never have children.
121     //
122     // these will be checks on the given node itself, and will be mutually
123     // exclusive.
124     if (node.nodeType == Node.ELEMENT_NODE) {
125         if (image_node_p (node)) {
126             var imageURL = image_node_url_spec (node); // variable for closures which follow.
128             // add methods for onImage Supertype
129             inherit ('image', 'copy_image', function () { call_interactively('copy-image-location',[imageURL]); });
130             // add methods for onMetaDataItem Supertype (Properties screen)
132             if (image_node_loaded_p (node)) {
133                 // add methods for onLoadedImage Supertype
134                 inherit ('image', 'save_image', function () { call_interactively('save-image',[makeURL(imageURL)]); });
135             }
136             if (! image_standalone_p (node)) {
137                 // add methods for notStandAloneImage Supertype
138                 inherit ('image', 'follow_image', function () { call_interactively('follow-image',[null, imageURL]); });
139             }
141         } else if (html_input_node_p (node) || html_textarea_node_p (node)) {
143             if (textbox_node_p (node)) {
144                 // add methods for onTextInput Supertype
145                 inherit ('edit', 'undo', function () { goDoCommand('cmd_undo'); });
146                 inherit ('edit', 'cut', function () { goDoCommand('cmd_cut'); });
147                 inherit ('edit', 'paste', function () { goDoCommand('cmd_paste'); });
148                 inherit ('edit', 'delete', function () { goDoCommand('cmd_delete'); });
149                 inherit ('edit', 'copy', function () { goDoCommand('cmd_copy'); });
150             }
151             // note, firefox also checks, onKeywordField = keyword_field_node_p (this.target);
153         } else if (html_html_node_p (node)) {
155             // pages with multiple <body>s are lame. we'll teach them a lesson.
156             var bodyElt = node.ownerDocument.getElementsByTagName("body")[0];
157             if (bodyElt) {
158                 var computedURL = get_computed_url (bodyElt, "background-image");
159                 if (computedURL) {
160                     // add methods for hasBGImage Supertype
161                     var bgImageURL = makeURLAbsolute (bodyElt.baseURI, computedURL);
162                     inherit ('background_image', 'follow_background_image', function () { call_interactively('follow-image',[null, bgImageURL]); });
163                 }
164             }
166         } else if ( "HTTPIndex" in content &&
167                     content.HTTPIndex instanceof Components.interfaces.nsIHTTPIndex ) {
168             // add methods for inDirList Supertype
169                 
170             // Bubble outward till we get to an element with URL attribute
171             // (which should be the href).
172             var root = node;
173             var link = null;
174             while (root && !link) {
175                 if (root.tagName == "tree") {
176                     // Hit root of tree; must have clicked in empty space;
177                     // thus, no link.
178                     break;
179                 }
180                 if (root.getAttribute ("URL")) {
181                     // provide all link methods as an interface, so either all
182                     // will be inherited, or none.
183                     var link_iface = {};
184                     // (inDirList && onLink) => open_link_in_frame and open_link_in_buffer
185                     var linkURL = root.getAttribute("URL");
186                     // add methods for onLink Supertype
187                     link_iface.copy_link = function () { /* XXX: make sure link is focused */ call_interactively('copy-link-location'); };
188                     link_iface.follow_link = function () { open_url_in (1, linkURL); };
189                     // add methods for (inDirList && onLink) Supertype
190                     link_iface.follow_link_in_buffer = function () { open_url_in (4, linkURL); };
191                     link_iface.follow_link_in_frame = function () { open_url_in (16, linkURL); };
193                     link = true;
195                     // If element is a directory, then you can't save it.
196                     if (root.getAttribute ("container") != "true") {
197                         // add methods for onSaveableLink Supertype
198                         link_iface.save_link = function () { /* XXX: make sure link is focused */ call_interactively ('save-focused-link'); };
199                     }
200                     inherit_interface ('link', link_iface);
201                 } else {
202                     root = root.parentNode;
203                 }
204             }
205         }
206     }
209     // Second, bubble out, looking for items of interest that can have childen.
210     // Always pick the innermost link, background image, etc.
211     //
212     const XMLNS = "http://www.w3.org/XML/1998/namespace";
213     var elem = node;
214     while (elem) {
215         if (elem.nodeType == Node.ELEMENT_NODE) {
217             // Link?
218             if ((elem instanceof HTMLAnchorElement && elem.href) ||
219                 elem instanceof HTMLAreaElement ||
220                 elem instanceof HTMLLinkElement ||
221                 elem.getAttributeNS ("http://www.w3.org/1999/xlink", "type") == "simple")
222             {
223                 var link_iface = {};
224                 var linkURL = get_link_url (elem);
225                 // add methods for onLink Supertype
226                 link_iface.copy_link = function () { call_interactively('copy-link-location', [linkURL]); };
227                 link_iface.follow_link = function () { open_url_in (1, linkURL); };
228                 link_iface.follow_link_in_buffer = function () { open_url_in (4, linkURL); };
229                 link_iface.follow_link_in_frame = function () { open_url_in (16, linkURL); };
231                 // add methods for onMetaDataItem Supertype
232                 // (none yet)
234                 if (get_link_uri (linkURL).scheme == "mailto") {
235                     // add methods for onMailtoLink Supertype
236                     link_iface.copy_email = function () { call_interactively ('copy-email-address', [linkURL]); };
237                 }
238                 if (url_saveable_p (linkURL)) {
239                     // add methods for onSaveableLink Supertype
240                     var linkURI = makeURL (linkURL);
241                     link_iface.save_link = function () { call_interactively ('save-focused-link', [linkURI]); };
242                 }
243                 inherit_interface ('link', link_iface);
244             }
246             // Metadata item?
247             //
248             // if we have not already inherited metadata methods, check for a metadata item now.
249             // if ( !this.onMetaDataItem ) {
250             //     this.onMetaDataItem = metadata_item_p (elem);
251             // }
253             // Background image?  Look for the computed background-image
254             // style.
255             //
256             var bgImgUrl = get_computed_url (elem, "background-image");
257             if (bgImgUrl) {
258                 // add methods for hasBGImage Supertype
259                 var bgImageURL = makeURLAbsolute (elem.baseURI, bgImgUrl);
260                 inherit ('background_image', 'follow_background_image', function () { call_interactively('follow-image',[null, bgImageURL]); });
261             }
262         }
263         elem = elem.parentNode;
264     }
266     // the isContentSelected superclass is has lower precedence than
267     // onTextInput.
268     if (content_selected_p ()) {
269         inherit ('edit', 'copy', function () { goDoCommand('cmd_copy'); });
270         inherit ('source', 'view_selection_source', function () { call_interactively ('view-partial-source'); });
271     }
272     inherit ('edit', 'select_all', function () { cmd_selectAll(); });
274     if (node_mathml_p (node)) {
275         inherit ('source', 'view_mathml_source', function () { call_interactively ('view-mathml-source',[null, node]); });
276     }
278     inherit ('source', 'view_source', function () { call_interactively('view-source'); });
280     return methods;